@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.
Files changed (55) hide show
  1. package/dist/chat/ChatMain.d.ts +9 -0
  2. package/dist/chat/ChatMain.d.ts.map +1 -0
  3. package/dist/chat/ChatMain.js +239 -0
  4. package/dist/chat/ChatSidebar.d.ts +13 -0
  5. package/dist/chat/ChatSidebar.d.ts.map +1 -0
  6. package/dist/chat/ChatSidebar.js +24 -0
  7. package/dist/chat/ConversationList.d.ts +12 -0
  8. package/dist/chat/ConversationList.d.ts.map +1 -0
  9. package/dist/chat/ConversationList.js +68 -0
  10. package/dist/chat/ExpensePanel.d.ts +8 -0
  11. package/dist/chat/ExpensePanel.d.ts.map +1 -0
  12. package/dist/chat/ExpensePanel.js +215 -0
  13. package/dist/chat/MessageBubble.d.ts +15 -0
  14. package/dist/chat/MessageBubble.d.ts.map +1 -0
  15. package/dist/chat/MessageBubble.js +197 -0
  16. package/dist/chat/MessageInput.d.ts +14 -0
  17. package/dist/chat/MessageInput.d.ts.map +1 -0
  18. package/dist/chat/MessageInput.js +235 -0
  19. package/dist/chat/MilestonePanel.d.ts +8 -0
  20. package/dist/chat/MilestonePanel.d.ts.map +1 -0
  21. package/dist/chat/MilestonePanel.js +173 -0
  22. package/dist/chat/dialogs/AddMemberDialog.d.ts +10 -0
  23. package/dist/chat/dialogs/AddMemberDialog.d.ts.map +1 -0
  24. package/dist/chat/dialogs/AddMemberDialog.js +110 -0
  25. package/dist/chat/dialogs/CameraDialog.d.ts +8 -0
  26. package/dist/chat/dialogs/CameraDialog.d.ts.map +1 -0
  27. package/dist/chat/dialogs/CameraDialog.js +128 -0
  28. package/dist/chat/dialogs/GroupInfoDialog.d.ts +10 -0
  29. package/dist/chat/dialogs/GroupInfoDialog.d.ts.map +1 -0
  30. package/dist/chat/dialogs/GroupInfoDialog.js +108 -0
  31. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts +9 -0
  32. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts.map +1 -0
  33. package/dist/chat/dialogs/GroupInfoViewDialog.js +16 -0
  34. package/dist/chat/dialogs/NewChatDialog.d.ts +2 -0
  35. package/dist/chat/dialogs/NewChatDialog.d.ts.map +1 -0
  36. package/dist/chat/dialogs/NewChatDialog.js +127 -0
  37. package/dist/chat/dialogs/NewGroupDialog.d.ts +2 -0
  38. package/dist/chat/dialogs/NewGroupDialog.d.ts.map +1 -0
  39. package/dist/chat/dialogs/NewGroupDialog.js +112 -0
  40. package/dist/chat/dialogs/ParticipantsDialog.d.ts +11 -0
  41. package/dist/chat/dialogs/ParticipantsDialog.d.ts.map +1 -0
  42. package/dist/chat/dialogs/ParticipantsDialog.js +143 -0
  43. package/dist/chat/index.d.ts +8 -0
  44. package/dist/chat/index.d.ts.map +1 -0
  45. package/dist/chat/index.js +7 -0
  46. package/dist/header/nav-item.d.ts +2 -1
  47. package/dist/header/nav-item.d.ts.map +1 -1
  48. package/dist/header/nav-item.js +2 -2
  49. package/dist/main/navbar.d.ts.map +1 -1
  50. package/dist/main/navbar.js +2 -2
  51. package/dist/misc/command.d.ts +3 -1
  52. package/dist/misc/command.d.ts.map +1 -1
  53. package/dist/misc/form.d.ts +1 -1
  54. package/dist/misc/form.d.ts.map +1 -1
  55. package/package.json +4 -3
@@ -0,0 +1,215 @@
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
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
39
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
40
+ if (ar || !(i in from)) {
41
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
42
+ ar[i] = from[i];
43
+ }
44
+ }
45
+ return to.concat(ar || Array.prototype.slice.call(from));
46
+ };
47
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
48
+ import { useState, useEffect } from "react";
49
+ import { chatApi } from "@myrjfa/state/dist/lib/actions/chat";
50
+ import { Receipt, Plus, Trash2, MoreVertical, Users, MapPin, Image as ImageIcon, Loader2, Navigation } from "lucide-react";
51
+ import { Button } from "../misc/button";
52
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../misc/dropdown-menu";
53
+ export function ExpensePanel(_a) {
54
+ var _this = this;
55
+ var conversationId = _a.conversationId, currentUserId = _a.currentUserId, _b = _a.isFullScreen, isFullScreen = _b === void 0 ? false : _b;
56
+ var _c = useState([]), expenses = _c[0], setExpenses = _c[1];
57
+ var _d = useState(null), balances = _d[0], setBalances = _d[1];
58
+ var _e = useState(true), loading = _e[0], setLoading = _e[1];
59
+ var _f = useState(false), isAdding = _f[0], setIsAdding = _f[1];
60
+ var _g = useState(""), newTitle = _g[0], setNewTitle = _g[1];
61
+ var _h = useState(""), newAmount = _h[0], setNewAmount = _h[1];
62
+ var _j = useState(null), newLocation = _j[0], setNewLocation = _j[1];
63
+ var _k = useState(null), receiptFile = _k[0], setReceiptFile = _k[1];
64
+ var _l = useState(false), uploading = _l[0], setUploading = _l[1];
65
+ var _m = useState(false), gettingLocation = _m[0], setGettingLocation = _m[1];
66
+ var fetchData = function () { return __awaiter(_this, void 0, void 0, function () {
67
+ var _a, expData, balData;
68
+ return __generator(this, function (_b) {
69
+ switch (_b.label) {
70
+ case 0:
71
+ setLoading(true);
72
+ return [4 /*yield*/, Promise.all([
73
+ chatApi.getExpenses(conversationId),
74
+ chatApi.getBalances(conversationId)
75
+ ])];
76
+ case 1:
77
+ _a = _b.sent(), expData = _a[0], balData = _a[1];
78
+ setExpenses(expData);
79
+ setBalances(balData);
80
+ setLoading(false);
81
+ return [2 /*return*/];
82
+ }
83
+ });
84
+ }); };
85
+ useEffect(function () {
86
+ fetchData();
87
+ }, [conversationId]);
88
+ var handleGetLocation = function () {
89
+ if (!navigator.geolocation) {
90
+ alert("Geolocation not supported by your browser");
91
+ return;
92
+ }
93
+ setGettingLocation(true);
94
+ navigator.geolocation.getCurrentPosition(function (position) { return __awaiter(_this, void 0, void 0, function () {
95
+ var _a, latitude, longitude, response, data, address, err_1;
96
+ var _b, _c, _d;
97
+ return __generator(this, function (_e) {
98
+ switch (_e.label) {
99
+ case 0:
100
+ _a = position.coords, latitude = _a.latitude, longitude = _a.longitude;
101
+ _e.label = 1;
102
+ case 1:
103
+ _e.trys.push([1, 4, , 5]);
104
+ return [4 /*yield*/, fetch("https://nominatim.openstreetmap.org/reverse?format=json&lat=".concat(latitude, "&lon=").concat(longitude), { headers: { 'Accept-Language': 'en' } })];
105
+ case 2:
106
+ response = _e.sent();
107
+ return [4 /*yield*/, response.json()];
108
+ case 3:
109
+ data = _e.sent();
110
+ address = ((_b = data.address) === null || _b === void 0 ? void 0 : _b.city) || ((_c = data.address) === null || _c === void 0 ? void 0 : _c.town) || ((_d = data.address) === null || _d === void 0 ? void 0 : _d.village) || "".concat(latitude.toFixed(4), ", ").concat(longitude.toFixed(4));
111
+ setNewLocation(address);
112
+ return [3 /*break*/, 5];
113
+ case 4:
114
+ err_1 = _e.sent();
115
+ // Fallback to coordinates if reverse geocoding fails
116
+ setNewLocation("".concat(latitude.toFixed(4), ", ").concat(longitude.toFixed(4)));
117
+ return [3 /*break*/, 5];
118
+ case 5:
119
+ setGettingLocation(false);
120
+ return [2 /*return*/];
121
+ }
122
+ });
123
+ }); }, function (error) {
124
+ console.error("Geolocation error:", error);
125
+ alert("Failed to get location. Please check permissions.");
126
+ setGettingLocation(false);
127
+ });
128
+ };
129
+ var handleCreate = function () { return __awaiter(_this, void 0, void 0, function () {
130
+ var receiptUrl, formData, res, amount, expense_1, balData, err_2;
131
+ return __generator(this, function (_a) {
132
+ switch (_a.label) {
133
+ case 0:
134
+ if (!newTitle.trim() || !newAmount)
135
+ return [2 /*return*/];
136
+ setUploading(true);
137
+ _a.label = 1;
138
+ case 1:
139
+ _a.trys.push([1, 7, 8, 9]);
140
+ receiptUrl = undefined;
141
+ if (!receiptFile) return [3 /*break*/, 3];
142
+ formData = new FormData();
143
+ formData.append("chatFiles", receiptFile);
144
+ return [4 /*yield*/, chatApi.uploadFiles(formData)];
145
+ case 2:
146
+ res = _a.sent();
147
+ if (res.data && res.data.files && res.data.files.length > 0) {
148
+ receiptUrl = res.data.files[0].location;
149
+ }
150
+ _a.label = 3;
151
+ case 3:
152
+ amount = parseFloat(newAmount);
153
+ return [4 /*yield*/, chatApi.createExpense(conversationId, {
154
+ title: newTitle.trim(),
155
+ amount: amount,
156
+ paidBy: [{ userId: currentUserId, amount: amount }],
157
+ location: newLocation || undefined,
158
+ receipt: receiptUrl
159
+ })];
160
+ case 4:
161
+ expense_1 = _a.sent();
162
+ if (!expense_1) return [3 /*break*/, 6];
163
+ setExpenses(function (prev) { return __spreadArray([expense_1], prev, true); });
164
+ setNewTitle("");
165
+ setNewAmount("");
166
+ setNewLocation(null);
167
+ setReceiptFile(null);
168
+ setIsAdding(false);
169
+ return [4 /*yield*/, chatApi.getBalances(conversationId)];
170
+ case 5:
171
+ balData = _a.sent();
172
+ setBalances(balData);
173
+ _a.label = 6;
174
+ case 6: return [3 /*break*/, 9];
175
+ case 7:
176
+ err_2 = _a.sent();
177
+ console.error("Failed to create expense:", err_2);
178
+ return [3 /*break*/, 9];
179
+ case 8:
180
+ setUploading(false);
181
+ return [7 /*endfinally*/];
182
+ case 9: return [2 /*return*/];
183
+ }
184
+ });
185
+ }); };
186
+ var handleDelete = function (id) { return __awaiter(_this, void 0, void 0, function () {
187
+ var balData;
188
+ return __generator(this, function (_a) {
189
+ switch (_a.label) {
190
+ case 0:
191
+ if (!window.confirm("Are you sure you want to delete this expense?")) return [3 /*break*/, 3];
192
+ return [4 /*yield*/, chatApi.deleteExpense(id)];
193
+ case 1:
194
+ _a.sent();
195
+ setExpenses(function (prev) { return prev.filter(function (e) { return e._id !== id; }); });
196
+ return [4 /*yield*/, chatApi.getBalances(conversationId)];
197
+ case 2:
198
+ balData = _a.sent();
199
+ setBalances(balData);
200
+ _a.label = 3;
201
+ case 3: return [2 /*return*/];
202
+ }
203
+ });
204
+ }); };
205
+ var panelClasses = isFullScreen
206
+ ? "w-full h-full flex flex-col"
207
+ : "flex flex-col h-full w-full md:w-80 border-l border-gray-200 dark:border-gray-700";
208
+ return (_jsxs("div", { className: "bg-white dark:bg-gray-900 ".concat(panelClasses), children: [_jsx("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0", children: _jsx("h3", { className: "font-semibold text-gray-900 dark:text-white", children: "Expenses" }) }), balances && (_jsxs("div", { className: "p-3 bg-teal-50 dark:bg-teal-900/20 mx-3 mt-3 rounded-xl border border-teal-100 dark:border-teal-800 flex-shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsx(Users, { className: "w-4 h-4 text-teal-500" }), _jsx("span", { className: "text-xs font-semibold text-teal-700 dark:text-teal-300 uppercase tracking-wider", children: "Group Balance" })] }), balances.balances.map(function (b) { return (_jsxs("div", { className: "flex justify-between text-xs py-1", children: [_jsx("span", { className: "text-gray-600 dark:text-gray-400", children: b.userId }), _jsxs("span", { className: "font-medium ".concat(b.netBalance >= 0 ? "text-green-600" : "text-red-500"), children: [b.netBalance >= 0 ? "+" : "", "\u20B9", b.netBalance.toFixed(2)] })] }, b.userId)); })] })), _jsx("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 pt-0", children: loading ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx("div", { className: "animate-spin w-5 h-5 border-2 border-teal-500 border-t-transparent rounded-full" }) })) : expenses.length === 0 && !isAdding ? (_jsx("div", { className: "text-center py-8 text-gray-400 text-sm", children: "No expenses yet" })) : (_jsxs(_Fragment, { children: [isAdding && (_jsxs("div", { className: "bg-teal-50 dark:bg-teal-900/20 p-3 rounded-xl border border-teal-100 dark:border-teal-800", children: [_jsx("input", { autoFocus: true, value: newTitle, onChange: function (e) { return setNewTitle(e.target.value); }, placeholder: "What for?", className: "w-full bg-white dark:bg-gray-800 border-none outline-none rounded-lg p-2 text-sm text-gray-900 dark:text-white mb-2 shadow-sm" }), _jsxs("div", { className: "relative mb-2", children: [_jsx("span", { className: "absolute left-2 top-2 text-gray-400 text-sm", children: "\u20B9" }), _jsx("input", { type: "number", value: newAmount, onChange: function (e) { return setNewAmount(e.target.value); }, placeholder: "0.00", className: "w-full bg-white dark:bg-gray-800 border-none outline-none rounded-lg p-2 pl-6 text-sm text-gray-900 dark:text-white shadow-sm" })] }), _jsxs("div", { className: "mb-2 flex gap-2", children: [_jsx("button", { onClick: handleGetLocation, disabled: gettingLocation || uploading, className: "flex-1 flex items-center justify-center gap-2 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed border border-gray-200 dark:border-gray-700 rounded-lg p-2 text-sm text-gray-600 dark:text-gray-300 transition-colors", children: gettingLocation ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-4 h-4 animate-spin" }), _jsx("span", { children: "Getting..." })] })) : (_jsxs(_Fragment, { children: [_jsx(Navigation, { className: "w-4 h-4" }), _jsx("span", { children: "Get Location" })] })) }), newLocation && (_jsx("button", { onClick: function () { return setNewLocation(null); }, className: "px-2 py-2 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 text-red-500 transition-colors", title: "Clear location", children: "\u2715" }))] }), newLocation && (_jsxs("div", { className: "mb-2 p-2 bg-white dark:bg-gray-800 rounded-lg border border-teal-200 dark:border-teal-800 flex items-center gap-2", children: [_jsx(MapPin, { className: "w-4 h-4 text-teal-500 flex-shrink-0" }), _jsx("span", { className: "text-xs text-gray-700 dark:text-gray-300 truncate", children: newLocation })] })), _jsxs("div", { className: "flex items-center gap-2 mb-2", children: [_jsxs("label", { className: "flex-1 flex items-center gap-2 text-xs text-gray-500 cursor-pointer hover:text-teal-500 transition-colors bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-2", children: [_jsx(ImageIcon, { className: "w-4 h-4" }), receiptFile ? _jsx("span", { className: "text-teal-500 truncate max-w-[150px] text-xs", children: receiptFile.name }) : "Add Receipt", _jsx("input", { type: "file", className: "hidden", accept: "image/*", onChange: function (e) { var _a; return setReceiptFile(((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) || null); } })] }), receiptFile && _jsx("button", { onClick: function () { return setReceiptFile(null); }, className: "text-red-400 hover:text-red-500 p-2", children: _jsx(Trash2, { className: "w-3 h-3" }) })] }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: function () {
209
+ setIsAdding(false);
210
+ setNewLocation(null);
211
+ }, disabled: uploading || gettingLocation, children: "Cancel" }), _jsxs(Button, { size: "sm", onClick: handleCreate, disabled: uploading || gettingLocation, className: "bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 text-white", children: [uploading && _jsx(Loader2, { className: "w-3 h-3 mr-1 animate-spin" }), "Add"] })] })] })), expenses.map(function (e) {
212
+ var _a;
213
+ return (_jsxs("div", { className: "group flex items-start gap-3 p-3 bg-gray-50 dark:bg-gray-800/30 border border-gray-100 dark:border-gray-800 rounded-xl transition-colors", children: [_jsx("div", { className: "mt-1 p-2 bg-teal-100 dark:bg-teal-900/30 rounded-lg text-teal-500 flex-shrink-0", children: _jsx(Receipt, { className: "w-4 h-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex justify-between items-start", children: [_jsx("p", { className: "text-sm font-semibold text-gray-800 dark:text-gray-200 truncate", children: e.title }), _jsxs("p", { className: "text-sm font-bold text-gray-900 dark:text-white flex-shrink-0 ml-2", children: ["\u20B9", e.amount] })] }), _jsxs("div", { className: "flex justify-between items-start mt-1 flex-wrap gap-1", children: [_jsxs("p", { className: "text-[10px] text-gray-400", children: ["by ", (_a = e.paidBy[0]) === null || _a === void 0 ? void 0 : _a.userId, " \u2022 ", new Date(e.date).toLocaleDateString()] }), (e.location || e.receipt) && (_jsxs("div", { className: "flex items-center gap-2 mt-1 flex-wrap", children: [e.location && (_jsxs("a", { href: e.location.startsWith('http') ? e.location : "https://maps.google.com/?q=".concat(e.location), target: "_blank", rel: "noopener noreferrer", className: "flex items-center text-[10px] text-blue-500 hover:underline", children: [_jsx(MapPin, { className: "w-3 h-3 mr-0.5" }), e.location.length > 20 ? e.location.substring(0, 20) + "..." : e.location] })), e.receipt && (_jsxs("a", { href: e.receipt, target: "_blank", rel: "noopener noreferrer", className: "flex items-center text-[10px] text-blue-500 hover:underline", children: [_jsx(Receipt, { className: "w-3 h-3 mr-0.5" }), "View"] }))] }))] })] }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: "opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-all mt-2 flex-shrink-0", children: _jsx(MoreVertical, { className: "w-3 h-3 text-gray-400" }) }) }), _jsx(DropdownMenuContent, { align: "end", children: _jsxs(DropdownMenuItem, { className: "text-red-500", onClick: function () { return handleDelete(e._id); }, children: [_jsx(Trash2, { className: "w-4 h-4 mr-2" }), "Delete"] }) })] })] }, e._id));
214
+ })] })) }), !isAdding && (_jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0", children: _jsxs(Button, { onClick: function () { return setIsAdding(true); }, className: "w-full bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 text-white rounded-lg flex items-center justify-center gap-2", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Expense"] }) }))] }));
215
+ }
@@ -0,0 +1,15 @@
1
+ import type { Message } from "@myrjfa/state/dist/lib/models/chat";
2
+ interface MessageBubbleProps {
3
+ message: Message;
4
+ isOwn: boolean;
5
+ showSender?: boolean;
6
+ senderProfilePic?: string;
7
+ onEdit?: (messageId: string, text: string) => void;
8
+ onDelete?: (messageId: string) => void;
9
+ onReply?: (message: Message) => void;
10
+ onReactionAdd?: (messageId: string, emoji: string) => void;
11
+ onScrollToReply?: (messageId: string) => void;
12
+ }
13
+ export declare function MessageBubble({ message, isOwn, showSender, senderProfilePic, onEdit, onDelete, onReply, onReactionAdd, onScrollToReply }: MessageBubbleProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=MessageBubble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageBubble.d.ts","sourceRoot":"","sources":["../../src/chat/MessageBubble.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAUlE,UAAU,kBAAkB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAWD,wBAAgB,aAAa,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,aAAa,EACb,eAAe,EAClB,EAAE,kBAAkB,2CAkdpB"}
@@ -0,0 +1,197 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Check, CheckCheck, Reply, MoreHorizontal, Pencil as PencilIcon, Trash2, PlusCircleIcon, Smile } from "lucide-react";
4
+ import { Avatar, AvatarFallback, AvatarImage } from "../misc/avatar";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../misc/dropdown-menu";
6
+ import { Button } from "../misc/button";
7
+ import { useEffect, useState, useRef } from "react";
8
+ import EmojiPicker from 'emoji-picker-react';
9
+ var QUICK_REACTIONS = [
10
+ { emoji: "❤️", label: "love" },
11
+ { emoji: "👍", label: "like" },
12
+ { emoji: "👎", label: "dislike" },
13
+ { emoji: "✅", label: "check" },
14
+ { emoji: "😠", label: "angry" },
15
+ { emoji: "😢", label: "sad" },
16
+ ];
17
+ export function MessageBubble(_a) {
18
+ var _b, _c, _d, _e;
19
+ var message = _a.message, isOwn = _a.isOwn, showSender = _a.showSender, senderProfilePic = _a.senderProfilePic, onEdit = _a.onEdit, onDelete = _a.onDelete, onReply = _a.onReply, onReactionAdd = _a.onReactionAdd, onScrollToReply = _a.onScrollToReply;
20
+ var _f = useState(false), showQuickReactions = _f[0], setShowQuickReactions = _f[1];
21
+ var _g = useState(false), showReactionPicker = _g[0], setShowReactionPicker = _g[1];
22
+ var _h = useState(false), showActionMenu = _h[0], setShowActionMenu = _h[1];
23
+ var _j = useState(false), showMobileMenu = _j[0], setShowMobileMenu = _j[1];
24
+ var reactionRef = useRef(null);
25
+ var actionRef = useRef(null);
26
+ var mobileMenuRef = useRef(null);
27
+ var mobilePopupRef = useRef(null);
28
+ var messageBubbleRef = useRef(null);
29
+ // Close reaction picker when clicking outside
30
+ useEffect(function () {
31
+ var handleClickOutside = function (event) {
32
+ if (reactionRef.current && !reactionRef.current.contains(event.target)) {
33
+ setShowQuickReactions(false);
34
+ setShowReactionPicker(false);
35
+ }
36
+ };
37
+ if (showQuickReactions || showReactionPicker) {
38
+ document.addEventListener("mousedown", handleClickOutside);
39
+ return function () {
40
+ document.removeEventListener("mousedown", handleClickOutside);
41
+ };
42
+ }
43
+ }, [showQuickReactions, showReactionPicker]);
44
+ // Close mobile menu when clicking outside
45
+ useEffect(function () {
46
+ var handleClickOutside = function (event) {
47
+ if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target)) {
48
+ setShowMobileMenu(false);
49
+ }
50
+ };
51
+ if (showMobileMenu) {
52
+ document.addEventListener("mousedown", handleClickOutside);
53
+ return function () {
54
+ document.removeEventListener("mousedown", handleClickOutside);
55
+ };
56
+ }
57
+ }, [showMobileMenu]);
58
+ // Position popup to stay within viewport and over message
59
+ useEffect(function () {
60
+ if (showMobileMenu && mobilePopupRef.current && messageBubbleRef.current) {
61
+ var popup_1 = mobilePopupRef.current;
62
+ var messageBubble = messageBubbleRef.current;
63
+ var bubbleRect = messageBubble.getBoundingClientRect();
64
+ var popupRect = popup_1.getBoundingClientRect();
65
+ var viewportHeight_1 = window.innerHeight;
66
+ var viewportWidth = window.innerWidth;
67
+ var padding_1 = 8;
68
+ // Reset styles
69
+ popup_1.style.top = "auto";
70
+ popup_1.style.bottom = "auto";
71
+ popup_1.style.left = "auto";
72
+ popup_1.style.right = "auto";
73
+ popup_1.style.transform = "none";
74
+ // Position horizontally - prefer the side with more space
75
+ if (isOwn) {
76
+ // Message is on right, try to position popup on left of message
77
+ var spaceOnLeft = bubbleRect.left;
78
+ var popupWidth = 256; // w-64 = 16rem = 256px
79
+ if (spaceOnLeft > popupWidth + padding_1) {
80
+ // Enough space on left
81
+ popup_1.style.right = "100%";
82
+ popup_1.style.marginRight = "8px";
83
+ }
84
+ else {
85
+ // Not enough space, position on right but keep within viewport
86
+ popup_1.style.left = "100%";
87
+ popup_1.style.marginLeft = "8px";
88
+ // If it goes out of right edge, adjust
89
+ var rect = popup_1.getBoundingClientRect();
90
+ if (rect.right > viewportWidth - padding_1) {
91
+ popup_1.style.left = "auto";
92
+ popup_1.style.right = "0";
93
+ }
94
+ }
95
+ }
96
+ else {
97
+ // Message is on left, try to position popup on right of message
98
+ var spaceOnRight = viewportWidth - bubbleRect.right;
99
+ var popupWidth = 256; // w-64 = 16rem = 256px
100
+ if (spaceOnRight > popupWidth + padding_1) {
101
+ // Enough space on right
102
+ popup_1.style.left = "100%";
103
+ popup_1.style.marginLeft = "8px";
104
+ }
105
+ else {
106
+ // Not enough space, position on left but keep within viewport
107
+ popup_1.style.right = "100%";
108
+ popup_1.style.marginRight = "8px";
109
+ // If it goes out of left edge, adjust
110
+ var rect = popup_1.getBoundingClientRect();
111
+ if (rect.left < padding_1) {
112
+ popup_1.style.right = "auto";
113
+ popup_1.style.left = "0";
114
+ }
115
+ }
116
+ }
117
+ // Position vertically - center on message
118
+ popup_1.style.top = "50%";
119
+ popup_1.style.transform = "translateY(-50%)";
120
+ // Make sure it doesn't go out of bottom
121
+ setTimeout(function () {
122
+ var rect = popup_1.getBoundingClientRect();
123
+ if (rect.bottom > viewportHeight_1 - padding_1) {
124
+ popup_1.style.top = "auto";
125
+ popup_1.style.bottom = "0";
126
+ popup_1.style.transform = "translateY(0)";
127
+ }
128
+ else if (rect.top < padding_1) {
129
+ popup_1.style.top = "0";
130
+ popup_1.style.bottom = "auto";
131
+ popup_1.style.transform = "translateY(0)";
132
+ }
133
+ }, 0);
134
+ }
135
+ }, [showMobileMenu, isOwn]);
136
+ if (message.type === "system" || message.type === "milestone" || message.type === "expense") {
137
+ return (_jsx("div", { className: "flex justify-center my-3", children: _jsx("div", { className: "bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs px-4 py-1.5 rounded-full max-w-xs text-center shadow-sm", children: message.content.text }) }));
138
+ }
139
+ var time = new Date(message.createdAt).toLocaleTimeString([], {
140
+ hour: "2-digit",
141
+ minute: "2-digit",
142
+ });
143
+ var linkRegex = /(https?:\/\/[^\s]+)/g;
144
+ var emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi;
145
+ var phoneRegex = /(\+?1?\d{9,15})/g;
146
+ var renderTextWithLinks = function (text) {
147
+ var parts = text.split(/\s+/);
148
+ return parts.map(function (part, i) {
149
+ if (linkRegex.test(part)) {
150
+ return (_jsxs("a", { href: part, target: "_blank", rel: "noopener noreferrer", className: "underline hover:opacity-80 break-all text-indigo-500", children: [part, " "] }, i));
151
+ }
152
+ if (emailRegex.test(part)) {
153
+ return (_jsxs("a", { href: "mailto:".concat(part), className: "underline hover:opacity-80 break-all text-indigo-500", children: [part, " "] }, i));
154
+ }
155
+ if (phoneRegex.test(part)) {
156
+ return (_jsxs("a", { href: "tel:".concat(part), className: "underline hover:opacity-80 break-all text-indigo-200", children: [part, " "] }, i));
157
+ }
158
+ return part + " ";
159
+ });
160
+ };
161
+ return (_jsxs("div", { className: "flex mb-2 ".concat(isOwn ? "justify-end" : "justify-start"), children: [!isOwn && (_jsxs(Avatar, { className: "w-8 h-8 mr-2 mt-1 flex-shrink-0", children: [_jsx(AvatarImage, { src: senderProfilePic }), _jsx(AvatarFallback, { children: message.senderId.charAt(0).toUpperCase() })] })), _jsxs("div", { className: "flex group items-end gap-1 ".concat(isOwn ? "flex-row-reverse" : "flex-row"), children: [_jsxs("div", { className: "max-w-xs sm:max-w-md md:max-w-lg flex flex-col ".concat(isOwn ? "items-end" : "items-start"), ref: messageBubbleRef, children: [showSender && !isOwn && (_jsx("span", { className: "text-xs font-medium text-teal-600 dark:text-teal-400 ml-1 mb-0.5", children: message.senderId })), message.replyTo && (_jsxs(Button, { onClick: function () { return onScrollToReply === null || onScrollToReply === void 0 ? void 0 : onScrollToReply(message.replyTo._id); }, className: "text-xs mb-2 px-3 py-2 rounded-lg border-l-2 max-w-full text-left hover:opacity-80 transition-opacity ".concat(isOwn
162
+ ? "bg-opacity-50 bg-teal-600/40 border-teal-300 text-teal-100"
163
+ : "bg-gray-200 dark:bg-gray-700 border-gray-400 dark:border-gray-600 text-gray-600 dark:text-gray-300"), children: [_jsxs("div", { className: "font-semibold text-xs truncate", children: ["\u21B3 ", (_b = message.replyTo) === null || _b === void 0 ? void 0 : _b.senderId] }), _jsx("div", { className: "truncate text-gray-500 dark:text-gray-400 text-xs", children: (_d = (_c = message.replyTo) === null || _c === void 0 ? void 0 : _c.content.text) === null || _d === void 0 ? void 0 : _d.substring(0, 50) })] })), _jsxs("div", { className: "px-4 py-2.5 rounded-2xl relative shadow-sm backdrop-blur-md\n ".concat(isOwn
164
+ ? "bg-gradient-to-r from-teal-500/70 to-teal-600/70 text-white rounded-br-md border border-teal-400/30"
165
+ : "bg-white dark:bg-gray-800 text-gray-900 dark:text-white rounded-bl-md border border-gray-100 dark:border-gray-700", "\n ").concat(message.isDeleted ? "opacity-60 italic" : ""), children: [message.content.media && message.content.media.length > 0 && (_jsx("div", { className: "mb-2 space-y-1", children: message.content.media.map(function (m, i) { return (_jsx("div", { children: m.type === "image" ? (_jsx("img", { src: m.url, alt: m.name, className: "rounded-lg max-w-full max-h-64 object-cover" })) : (_jsxs("a", { href: m.url, className: "text-sm underline", target: "_blank", rel: "noopener noreferrer", children: ["\uD83D\uDCCE ", m.name] })) }, i)); }) })), _jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: renderTextWithLinks((_e = message.content.text) !== null && _e !== void 0 ? _e : "") }), _jsxs("div", { className: "flex items-center justify-end gap-1 mt-1 ".concat(isOwn ? "text-teal-200" : "text-gray-400"), children: [message.isEdited && (_jsx(PencilIcon, { className: "w-3 h-3" })), _jsx("span", { className: "text-[10px]", children: time }), isOwn && (message.readBy.length > 1 ? (_jsx(CheckCheck, { className: "w-4 h-4 text-sky-300" })) : (_jsx(Check, { className: "w-4 h-4" })))] })] }), message.reactions && message.reactions.length > 0 && (_jsx("div", { className: "flex gap-1 mt-2 ".concat(isOwn ? "justify-end" : "justify-start", " px-2"), children: Object.entries(message.reactions.reduce(function (acc, r) {
166
+ acc[r.emoji] = (acc[r.emoji] || 0) + 1;
167
+ return acc;
168
+ }, {})).map(function (_a) {
169
+ var emoji = _a[0], count = _a[1];
170
+ return (_jsxs("span", { className: "bg-gray-100 dark:bg-gray-700 rounded-full px-2 py-0.5 text-xs shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 cursor-pointer transition-colors", children: [emoji, " ", count > 1 && count] }, emoji));
171
+ }) }))] }), !message.isDeleted && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "hidden md:flex gap-1 items-center flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity my-auto", children: [_jsxs("div", { className: "relative", ref: reactionRef, children: [_jsx("button", { onClick: function () {
172
+ setShowQuickReactions(!showQuickReactions);
173
+ setShowReactionPicker(false);
174
+ }, className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", title: "Add reaction", children: _jsx(Smile, { className: "w-4 h-4" }) }), (showQuickReactions || showReactionPicker) && (_jsx("div", { className: "absolute z-50 bottom-10 ".concat(isOwn ? "right-0" : "left-0", " whitespace-nowrap"), children: _jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-xl shadow-xl border border-gray-200 dark:border-gray-700 p-2", children: [showQuickReactions && (_jsxs("div", { className: "flex gap-1 flex-wrap w-max", children: [QUICK_REACTIONS.map(function (reaction) { return (_jsx("button", { onClick: function () {
175
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
176
+ setShowQuickReactions(false);
177
+ }, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", title: reaction.label, children: reaction.emoji }, reaction.label)); }), _jsx("button", { onClick: function () {
178
+ setShowQuickReactions(false);
179
+ setShowReactionPicker(true);
180
+ }, className: "text-xl hover:scale-125 transition-transform hover:bg-gray-100 dark:hover:bg-gray-700 p-2 rounded-lg", children: _jsx(PlusCircleIcon, { className: "w-5 h-5 text-gray-600 dark:text-gray-300" }) })] })), showReactionPicker && (_jsx("div", { className: "mt-2 border-t border-gray-200 dark:border-gray-700 pt-2", children: _jsx(EmojiPicker, { onEmojiClick: function (emojiObject) {
181
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, emojiObject.emoji);
182
+ setShowReactionPicker(false);
183
+ setShowQuickReactions(false);
184
+ } }) }))] }) }))] }), _jsx("div", { className: "relative", ref: actionRef, children: _jsxs(DropdownMenu, { open: showActionMenu, onOpenChange: setShowActionMenu, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) }) }), _jsxs(DropdownMenuContent, { align: isOwn ? "end" : "start", className: "w-48", children: [_jsxs(DropdownMenuItem, { onClick: function () { return onReply === null || onReply === void 0 ? void 0 : onReply(message); }, children: [_jsx(Reply, { className: "w-4 h-4 mr-2" }), "Reply"] }), isOwn && _jsxs(DropdownMenuItem, { onClick: function () { return onEdit === null || onEdit === void 0 ? void 0 : onEdit(message._id, message.content.text || ""); }, children: [_jsx(PencilIcon, { className: "w-4 h-4 mr-2" }), "Edit"] }), isOwn && _jsxs(DropdownMenuItem, { className: "text-red-500", onClick: function () { return onDelete === null || onDelete === void 0 ? void 0 : onDelete(message._id); }, children: [_jsx(Trash2, { className: "w-4 h-4 mr-2" }), "Unsend"] })] })] }) })] }), _jsxs("div", { className: "md:hidden relative my-auto", ref: mobileMenuRef, children: [_jsx("button", { onClick: function () { return setShowMobileMenu(!showMobileMenu); }, className: "flex items-center justify-center w-8 h-8 rounded-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors shadow-md flex-shrink-0", children: _jsx(MoreHorizontal, { className: "w-4 h-4" }) }), showMobileMenu && (_jsx("div", { className: "fixed inset-0 z-40", onClick: function () { return setShowMobileMenu(false); } })), showMobileMenu && (_jsx("div", { ref: mobilePopupRef, className: "absolute z-50 bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden w-64 max-h-96 overflow-y-auto", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "p-2 border-b border-gray-200 dark:border-gray-700", children: _jsx("div", { className: "flex gap-1 flex-wrap justify-center", children: QUICK_REACTIONS.map(function (reaction) { return (_jsx("button", { onClick: function () {
185
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
186
+ setShowMobileMenu(false);
187
+ }, className: "text-lg hover:scale-125 transition-transform p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded", title: reaction.label, children: reaction.emoji }, reaction.label)); }) }) }), _jsxs("button", { onClick: function () {
188
+ onReply === null || onReply === void 0 ? void 0 : onReply(message);
189
+ setShowMobileMenu(false);
190
+ }, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(Reply, { className: "w-4 h-4" }), "Reply"] }), isOwn && (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: function () {
191
+ onEdit === null || onEdit === void 0 ? void 0 : onEdit(message._id, message.content.text || "");
192
+ setShowMobileMenu(false);
193
+ }, className: "px-4 py-2.5 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 border-b border-gray-200 dark:border-gray-700", children: [_jsx(PencilIcon, { className: "w-4 h-4" }), "Edit"] }), _jsxs("button", { onClick: function () {
194
+ onDelete === null || onDelete === void 0 ? void 0 : onDelete(message._id);
195
+ setShowMobileMenu(false);
196
+ }, className: "px-4 py-2.5 text-sm text-left text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 flex items-center gap-2", children: [_jsx(Trash2, { className: "w-4 h-4" }), "Unsend"] })] }))] }) }))] })] }))] })] }));
197
+ }
@@ -0,0 +1,14 @@
1
+ interface MessageInputProps {
2
+ onSend: (text: string, files?: File[]) => void;
3
+ onTypingStart?: () => void;
4
+ onTypingStop?: () => void;
5
+ disabled?: boolean;
6
+ replyTo?: any;
7
+ onCancelReply?: () => void;
8
+ onAddMilestone?: () => void;
9
+ onAddExpense?: () => void;
10
+ activePanel?: "chat" | "milestones" | "expenses";
11
+ }
12
+ export declare function MessageInput({ onSend, onTypingStart, onTypingStop, disabled, replyTo, onCancelReply, onAddMilestone, onAddExpense, activePanel }: MessageInputProps): import("react/jsx-runtime").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=MessageInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/chat/MessageInput.tsx"],"names":[],"mappings":"AAWA,UAAU,iBAAiB;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC;CACpD;AAED,wBAAgB,YAAY,CAAC,EACzB,MAAM,EACN,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAAoB,EACvB,EAAE,iBAAiB,2CAwSnB"}