@myrjfa/ui 1.0.8 → 1.0.10

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 (161) 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 +272 -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 +23 -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 +70 -0
  10. package/dist/chat/ExpensePanel.d.ts +11 -0
  11. package/dist/chat/ExpensePanel.d.ts.map +1 -0
  12. package/dist/chat/ExpensePanel.js +235 -0
  13. package/dist/chat/MessageBubble.d.ts +18 -0
  14. package/dist/chat/MessageBubble.d.ts.map +1 -0
  15. package/dist/chat/MessageBubble.js +284 -0
  16. package/dist/chat/MessageInput.d.ts +21 -0
  17. package/dist/chat/MessageInput.d.ts.map +1 -0
  18. package/dist/chat/MessageInput.js +252 -0
  19. package/dist/chat/MilestonePanel.d.ts +11 -0
  20. package/dist/chat/MilestonePanel.d.ts.map +1 -0
  21. package/dist/chat/MilestonePanel.js +173 -0
  22. package/dist/chat/components/ParticipantSelector.d.ts +11 -0
  23. package/dist/chat/components/ParticipantSelector.d.ts.map +1 -0
  24. package/dist/chat/components/ParticipantSelector.js +17 -0
  25. package/dist/chat/components/SplitInputs.d.ts +11 -0
  26. package/dist/chat/components/SplitInputs.d.ts.map +1 -0
  27. package/dist/chat/components/SplitInputs.js +83 -0
  28. package/dist/chat/components/SplitTypeSelector.d.ts +8 -0
  29. package/dist/chat/components/SplitTypeSelector.d.ts.map +1 -0
  30. package/dist/chat/components/SplitTypeSelector.js +13 -0
  31. package/dist/chat/dialogs/AddMemberDialog.d.ts +10 -0
  32. package/dist/chat/dialogs/AddMemberDialog.d.ts.map +1 -0
  33. package/dist/chat/dialogs/AddMemberDialog.js +50 -0
  34. package/dist/chat/dialogs/CameraDialog.d.ts +8 -0
  35. package/dist/chat/dialogs/CameraDialog.d.ts.map +1 -0
  36. package/dist/chat/dialogs/CameraDialog.js +80 -0
  37. package/dist/chat/dialogs/CreatePollDialog.d.ts +8 -0
  38. package/dist/chat/dialogs/CreatePollDialog.d.ts.map +1 -0
  39. package/dist/chat/dialogs/CreatePollDialog.js +38 -0
  40. package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts +19 -0
  41. package/dist/chat/dialogs/ExpenseAnalyticsDialog.d.ts.map +1 -0
  42. package/dist/chat/dialogs/ExpenseAnalyticsDialog.js +52 -0
  43. package/dist/chat/dialogs/GroupInfoDialog.d.ts +10 -0
  44. package/dist/chat/dialogs/GroupInfoDialog.d.ts.map +1 -0
  45. package/dist/chat/dialogs/GroupInfoDialog.js +71 -0
  46. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts +9 -0
  47. package/dist/chat/dialogs/GroupInfoViewDialog.d.ts.map +1 -0
  48. package/dist/chat/dialogs/GroupInfoViewDialog.js +15 -0
  49. package/dist/chat/dialogs/NewChatDialog.d.ts +2 -0
  50. package/dist/chat/dialogs/NewChatDialog.d.ts.map +1 -0
  51. package/dist/chat/dialogs/NewChatDialog.js +63 -0
  52. package/dist/chat/dialogs/NewGroupDialog.d.ts +2 -0
  53. package/dist/chat/dialogs/NewGroupDialog.d.ts.map +1 -0
  54. package/dist/chat/dialogs/NewGroupDialog.js +69 -0
  55. package/dist/chat/dialogs/ParticipantsDialog.d.ts +11 -0
  56. package/dist/chat/dialogs/ParticipantsDialog.d.ts.map +1 -0
  57. package/dist/chat/dialogs/ParticipantsDialog.js +93 -0
  58. package/dist/chat/dialogs/UserProfileDialog.d.ts +10 -0
  59. package/dist/chat/dialogs/UserProfileDialog.d.ts.map +1 -0
  60. package/dist/chat/dialogs/UserProfileDialog.js +100 -0
  61. package/dist/chat/index.d.ts +8 -0
  62. package/dist/chat/index.d.ts.map +1 -0
  63. package/dist/chat/index.js +7 -0
  64. package/dist/dashboard/action-buttons.js +4 -5
  65. package/dist/dashboard/app-sidebar.js +46 -128
  66. package/dist/dashboard/card-header.js +1 -2
  67. package/dist/dashboard/dashboard-components.js +3 -5
  68. package/dist/dashboard/data-table.d.ts +18 -18
  69. package/dist/dashboard/data-table.js +54 -66
  70. package/dist/dashboard/nav-main.js +4 -5
  71. package/dist/dashboard/nav-secondary.js +2 -25
  72. package/dist/dashboard/stats-card.js +1 -2
  73. package/dist/dashboard/table.js +8 -54
  74. package/dist/header/mobile-nav-item.js +1 -2
  75. package/dist/header/mobile-navbar.js +8 -9
  76. package/dist/header/nav-item.d.ts +2 -1
  77. package/dist/header/nav-item.d.ts.map +1 -1
  78. package/dist/header/nav-item.js +2 -3
  79. package/dist/index.d.ts +57 -56
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +57 -57
  82. package/dist/main/footer.js +5 -9
  83. package/dist/main/navbar.d.ts.map +1 -1
  84. package/dist/main/navbar.js +53 -136
  85. package/dist/main/paginationBar.js +12 -14
  86. package/dist/main/portfolio/portfolioItem.js +1 -2
  87. package/dist/main/portfolio/portfolioPopup.js +16 -17
  88. package/dist/main/review-page.d.ts.map +1 -1
  89. package/dist/main/review-page.js +131 -198
  90. package/dist/misc/accordion.js +8 -34
  91. package/dist/misc/alert-dialog.js +22 -55
  92. package/dist/misc/alert.js +7 -32
  93. package/dist/misc/alertdialogclaude.js +50 -125
  94. package/dist/misc/avatar.js +6 -31
  95. package/dist/misc/badge.d.ts +8 -8
  96. package/dist/misc/badge.js +4 -27
  97. package/dist/misc/button.d.ts +9 -9
  98. package/dist/misc/button.js +4 -27
  99. package/dist/misc/card.js +12 -40
  100. package/dist/misc/carousel.js +37 -61
  101. package/dist/misc/checkbox.js +2 -25
  102. package/dist/misc/command.d.ts +3 -1
  103. package/dist/misc/command.d.ts.map +1 -1
  104. package/dist/misc/command.js +18 -49
  105. package/dist/misc/datepicker.js +66 -76
  106. package/dist/misc/dialog.js +20 -52
  107. package/dist/misc/dropdown-menu.js +30 -67
  108. package/dist/misc/faq.js +2 -3
  109. package/dist/misc/file-preview.js +43 -97
  110. package/dist/misc/filter.d.ts.map +1 -1
  111. package/dist/misc/filter.js +18 -21
  112. package/dist/misc/filterBar.js +136 -160
  113. package/dist/misc/form.d.ts +1 -1
  114. package/dist/misc/form.d.ts.map +1 -1
  115. package/dist/misc/form.js +39 -60
  116. package/dist/misc/image-cropper.d.ts +10 -0
  117. package/dist/misc/image-cropper.d.ts.map +1 -0
  118. package/dist/misc/image-cropper.js +44 -0
  119. package/dist/misc/input.js +2 -25
  120. package/dist/misc/label.js +2 -25
  121. package/dist/misc/line-carousel.js +3 -13
  122. package/dist/misc/multiImageUploader.d.ts +8 -8
  123. package/dist/misc/multiImageUploader.js +10 -11
  124. package/dist/misc/newsLetter.js +18 -78
  125. package/dist/misc/notification.d.ts +1 -2
  126. package/dist/misc/notification.d.ts.map +1 -1
  127. package/dist/misc/notification.js +89 -125
  128. package/dist/misc/pagination.js +16 -45
  129. package/dist/misc/password-eye.js +2 -3
  130. package/dist/misc/popover.d.ts.map +1 -1
  131. package/dist/misc/popover.js +8 -34
  132. package/dist/misc/progress.js +2 -25
  133. package/dist/misc/scroll-area.js +5 -29
  134. package/dist/misc/searchBar.js +27 -31
  135. package/dist/misc/searchBox.js +18 -22
  136. package/dist/misc/select.d.ts.map +1 -1
  137. package/dist/misc/select.js +22 -54
  138. package/dist/misc/separator.js +2 -25
  139. package/dist/misc/share.js +48 -104
  140. package/dist/misc/sheet.js +21 -53
  141. package/dist/misc/sidebar.d.ts +69 -69
  142. package/dist/misc/sidebar.js +112 -153
  143. package/dist/misc/skeleton.d.ts +2 -2
  144. package/dist/misc/skeleton.js +6 -29
  145. package/dist/misc/slider.js +3 -26
  146. package/dist/misc/starRating.js +5 -15
  147. package/dist/misc/stateCard.js +7 -8
  148. package/dist/misc/switch.d.ts +5 -0
  149. package/dist/misc/switch.d.ts.map +1 -0
  150. package/dist/misc/switch.js +8 -0
  151. package/dist/misc/tabs.js +8 -34
  152. package/dist/misc/textarea.js +2 -25
  153. package/dist/misc/tile.js +31 -92
  154. package/dist/misc/tooltip.js +8 -34
  155. package/dist/profile/socialLink.d.ts +1 -1
  156. package/dist/profile/socialLink.d.ts.map +1 -1
  157. package/dist/profile/socialLink.js +53 -69
  158. package/dist/utils/permissions.d.ts +7 -0
  159. package/dist/utils/permissions.d.ts.map +1 -0
  160. package/dist/utils/permissions.js +38 -0
  161. package/package.json +8 -5
@@ -0,0 +1,235 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { chatApi } from "@myrjfa/state/dist/lib/actions/chat";
5
+ import { Receipt, Plus, Trash2, Pencil, CreditCard, Users, MapPin, Image as ImageIcon, Loader2, Navigation, Lock, Unlock, ReceiptText, BarChart3, CheckCircle, X, ChevronDown, PiggyBankIcon } from "lucide-react";
6
+ import { Button } from "../misc/button";
7
+ import { Input } from "../misc/input";
8
+ import { Textarea } from "../misc/textarea";
9
+ import { confirmActions, deleteActions, showAlert } from "../misc/alertdialogclaude";
10
+ import { ExpenseAnalyticsDialog } from "./dialogs/ExpenseAnalyticsDialog";
11
+ import { SplitTypeSelector } from "./components/SplitTypeSelector";
12
+ import { SplitInputs } from "./components/SplitInputs";
13
+ import { ParticipantSelector } from "./components/ParticipantSelector";
14
+ import { Label } from "../misc/label";
15
+ export function ExpensePanel({ conversationId, currentUserId, isFullScreen = false, isFeatureEnabled = false, isAdmin = false, onEnableFeature }) {
16
+ const [expenses, setExpenses] = useState([]);
17
+ const [balances, setBalances] = useState(null);
18
+ const [loading, setLoading] = useState(true);
19
+ const [isAdding, setIsAdding] = useState(false);
20
+ const [showSettleMode, setShowSettleMode] = useState(false);
21
+ const [newTitle, setNewTitle] = useState("");
22
+ const [newAmount, setNewAmount] = useState("");
23
+ const [newCategory, setNewCategory] = useState("");
24
+ const [newDescription, setNewDescription] = useState("");
25
+ const [newLocation, setNewLocation] = useState(null);
26
+ const [receiptFile, setReceiptFile] = useState(null);
27
+ const [uploading, setUploading] = useState(false);
28
+ const [gettingLocation, setGettingLocation] = useState(false);
29
+ const [categories, setCategories] = useState([]);
30
+ const [showAnalytics, setShowAnalytics] = useState(false);
31
+ const [settling, setSettling] = useState(null);
32
+ const [expenseId, setExpenseId] = useState(null);
33
+ const [groupParticipants, setGroupParticipants] = useState([]);
34
+ const [showMoreOptions, setShowMoreOptions] = useState(false);
35
+ const [splitType, setSplitType] = useState("equal");
36
+ const [splitParticipants, setSplitParticipants] = useState([]);
37
+ const [splits, setSplits] = useState([]);
38
+ const [expandedExpenseId, setExpandedExpenseId] = useState(null);
39
+ const [showSplitModal, setShowSplitModal] = useState(false);
40
+ const [selectedExpenseForSplit, setSelectedExpenseForSplit] = useState(null);
41
+ const fetchData = async () => {
42
+ var _a;
43
+ setLoading(true);
44
+ const [expData, balData, convData] = await Promise.all([
45
+ chatApi.getExpenses(conversationId),
46
+ chatApi.getBalances(conversationId),
47
+ chatApi.getConversation(conversationId)
48
+ ]);
49
+ setExpenses(expData);
50
+ setBalances(balData);
51
+ const participants = ((_a = convData === null || convData === void 0 ? void 0 : convData.participants) === null || _a === void 0 ? void 0 : _a.map((p) => p.userId)) || [];
52
+ setGroupParticipants(participants);
53
+ setLoading(false);
54
+ };
55
+ useEffect(() => {
56
+ fetchData();
57
+ fetchCategories();
58
+ }, [conversationId]);
59
+ const fetchCategories = async () => {
60
+ try {
61
+ const cats = await chatApi.getExpenseCategories(conversationId);
62
+ setCategories(cats || []);
63
+ }
64
+ catch (error) {
65
+ console.error("Failed to fetch categories:", error);
66
+ }
67
+ };
68
+ const userOwesAmount = () => {
69
+ if (!balances)
70
+ return 0;
71
+ const userBalance = balances.balances.find(b => b.userId === currentUserId);
72
+ return userBalance && userBalance.netBalance < -0.01 ? Math.abs(userBalance.netBalance) : 0;
73
+ };
74
+ const getUserSettlements = () => {
75
+ if (!(balances === null || balances === void 0 ? void 0 : balances.settlements))
76
+ return { owes: [], isOwed: [] };
77
+ const owes = balances.settlements.filter(s => s.from === currentUserId);
78
+ const isOwed = balances.settlements.filter(s => s.to === currentUserId);
79
+ return { owes, isOwed };
80
+ };
81
+ const handleGetLocation = () => {
82
+ if (!navigator.geolocation) {
83
+ alert("Geolocation not supported by your browser");
84
+ return;
85
+ }
86
+ setGettingLocation(true);
87
+ navigator.geolocation.getCurrentPosition(async (position) => {
88
+ const { latitude, longitude } = position.coords;
89
+ const mapLink = `https://www.google.com/maps?q=${latitude},${longitude}`;
90
+ setNewLocation(mapLink);
91
+ setGettingLocation(false);
92
+ }, (error) => {
93
+ console.error("Error getting location:", error);
94
+ showAlert("Could not get location.");
95
+ setGettingLocation(false);
96
+ });
97
+ };
98
+ const resetForm = () => {
99
+ setNewTitle("");
100
+ setNewAmount("");
101
+ setNewCategory("");
102
+ setNewDescription("");
103
+ setNewLocation(null);
104
+ setReceiptFile(null);
105
+ setShowMoreOptions(false);
106
+ setIsAdding(false);
107
+ setExpenseId(null);
108
+ setSplitType("equal");
109
+ setSplitParticipants([]);
110
+ setSplits([]);
111
+ };
112
+ const handleCreate = async () => {
113
+ var _a, _b;
114
+ if (!newTitle.trim() || !newAmount)
115
+ return;
116
+ const amount = parseFloat(newAmount);
117
+ if (amount <= 0) {
118
+ showAlert("Amount must be greater than 0");
119
+ return;
120
+ }
121
+ setUploading(true);
122
+ try {
123
+ let receiptUrl = undefined;
124
+ if (receiptFile) {
125
+ const formData = new FormData();
126
+ formData.append("chatFiles", receiptFile);
127
+ const res = await chatApi.uploadFiles(formData);
128
+ if ((_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) {
129
+ receiptUrl = res.data.files[0].location;
130
+ }
131
+ }
132
+ const expenseData = {
133
+ title: newTitle.trim(),
134
+ amount,
135
+ category: newCategory.trim() || "General",
136
+ description: newDescription.trim() || undefined,
137
+ paidBy: [{ userId: currentUserId, amount }],
138
+ splitType,
139
+ splits: splitType != "equal" && splits,
140
+ location: newLocation || undefined,
141
+ receipt: receiptUrl
142
+ };
143
+ const expenseResponse = expenseId
144
+ ? await chatApi.updateExpense(expenseId, expenseData)
145
+ : await chatApi.createExpense(conversationId, expenseData);
146
+ if (expenseResponse.error) {
147
+ console.error("Failed to create expense:", expenseResponse.error);
148
+ await showAlert(expenseResponse.error, "Failed to create expense");
149
+ return;
150
+ }
151
+ else {
152
+ if (expenseResponse.data) {
153
+ await fetchData();
154
+ resetForm();
155
+ }
156
+ }
157
+ }
158
+ catch (err) {
159
+ console.error("Failed to create expense:", err);
160
+ showAlert(err, "Failed to create expense");
161
+ }
162
+ finally {
163
+ setUploading(false);
164
+ }
165
+ };
166
+ const handleDelete = async (id) => {
167
+ let confirmDelete = true;
168
+ await showAlert("Are you sure you want to delete this expense?", undefined, deleteActions(() => confirmDelete = false));
169
+ if (confirmDelete) {
170
+ const response = await chatApi.deleteExpense(id);
171
+ if (response.error) {
172
+ console.log("Unable to delete expense:" + id + "Error: " + response.error);
173
+ await showAlert("Unable to delete expense");
174
+ return;
175
+ }
176
+ setExpenses(prev => prev.filter(e => e._id !== id));
177
+ const balData = await chatApi.getBalances(conversationId);
178
+ setBalances(balData);
179
+ }
180
+ };
181
+ const handleEdit = async (expense) => {
182
+ setExpenseId(expense._id);
183
+ setNewTitle(expense.title);
184
+ setNewAmount(expense.amount.toString());
185
+ setNewCategory(expense.category || "");
186
+ setNewDescription(expense.description || "");
187
+ setNewLocation(expense.location || null);
188
+ setSplitType(expense.splitType);
189
+ setSplitParticipants(expense.splits.map(s => s.userId));
190
+ setSplits(expense.splits);
191
+ setIsAdding(true);
192
+ };
193
+ const handleSettleSplit = async (payment) => {
194
+ setSettling(payment.from + payment.to);
195
+ let confirmPayment = true;
196
+ await showAlert(`Pay ₹${payment.amount.toFixed(0)} to ${payment.to}`, undefined, confirmActions(() => confirmPayment = false));
197
+ if (confirmPayment) {
198
+ const response = await chatApi.settleSplit(conversationId, payment.to, payment.amount);
199
+ if (response.error) {
200
+ console.log("Error recording payment: " + response.error);
201
+ showAlert("Error recording payment");
202
+ }
203
+ else {
204
+ setExpenses(prev => [response.data, ...prev]);
205
+ }
206
+ }
207
+ setSettling(null);
208
+ };
209
+ const panelClasses = isFullScreen
210
+ ? "w-full h-full flex flex-col"
211
+ : "flex flex-col h-full w-full md:w-80 border-l border-gray-200 dark:border-gray-700";
212
+ return (_jsxs("div", { className: `bg-white dark:bg-gray-900 ${panelClasses}`, children: [_jsxs("div", { className: "p-4 border-b border-gray-200 dark:border-gray-700 flex-shrink-0 flex items-center justify-between", children: [_jsx("h3", { className: "font-semibold text-gray-900 dark:text-white", children: "Expenses" }), expenses.length > 0 && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => setShowAnalytics(prev => !prev), className: "text-teal-600 hover:text-teal-700 dark:text-teal-400", children: [_jsx(BarChart3, { className: "w-4 h-4 mr-1" }), "Analytics"] }))] }), !isAdding && 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((b) => (_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 ${b.netBalance >= 0 ? "text-green-600" : "text-red-500"}`, children: [b.netBalance >= 0 ? "+" : "", "\u20B9", b.netBalance.toFixed(2)] })] }, b.userId)))] })), showSettleMode && 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 justify-between gap-2 mb-3", children: [_jsxs("div", { className: "flex items-center gap-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: "Your Settlements" })] }), _jsx(Button, { onClick: () => setShowSettleMode(false), variant: "ghost", size: "sm", className: "h-6 w-6 p-0", children: _jsx(X, { className: "w-4 h-4" }) })] }), (() => {
213
+ const { owes, isOwed } = getUserSettlements();
214
+ const hasSettlements = owes.length > 0 || isOwed.length > 0;
215
+ return !hasSettlements ? (_jsx("p", { className: "text-xs text-gray-500 text-center py-4", children: "All settled up! \uD83C\uDF89" })) : (_jsxs("div", { className: "space-y-3", children: [owes.length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-red-600 dark:text-red-400 uppercase tracking-wider mb-2", children: "You Owe" }), _jsx("div", { className: "space-y-2", children: owes.map((s, i) => (_jsxs("div", { className: "flex items-center justify-between p-2.5 bg-white dark:bg-gray-800 rounded-lg border border-red-100 dark:border-red-900/30 hover:border-red-200 dark:hover:border-red-800 transition-colors", children: [_jsx("div", { className: "flex-1", children: _jsxs("p", { className: "text-xs text-gray-700 dark:text-gray-300", children: ["Pay ", _jsxs("span", { className: "font-semibold text-red-600 dark:text-red-400", children: ["\u20B9", s.amount.toFixed(0)] }), " to ", _jsx("span", { className: "font-semibold", children: s.to })] }) }), _jsxs(Button, { size: "sm", onClick: () => handleSettleSplit(s), disabled: settling === s.from + s.to, className: "text-[10px] text-white bg-red-500 hover:bg-red-600 dark:bg-red-600 dark:hover:bg-red-700 h-6 px-2 ml-2 flex-shrink-0", children: [_jsx(CheckCircle, { className: "w-3 h-3 mr-1" }), settling === s.from + s.to ? "..." : "Settle"] })] }, i))) })] })), isOwed.length > 0 && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-green-600 dark:text-green-400 uppercase tracking-wider mb-2", children: "You Are Owed" }), _jsx("div", { className: "space-y-2", children: isOwed.map((s, i) => (_jsx("div", { className: "flex items-center justify-between p-2.5 bg-white dark:bg-gray-800 rounded-lg border border-green-100 dark:border-green-900/30 hover:border-green-200 dark:hover:border-green-800 transition-colors", children: _jsxs("p", { className: "text-xs text-gray-700 dark:text-gray-300", children: [_jsx("span", { className: "font-semibold", children: s.from }), " owes you", " ", _jsxs("span", { className: "font-semibold text-green-600 dark:text-green-400", children: ["\u20B9", s.amount.toFixed(0)] })] }) }, i))) })] }))] }));
216
+ })()] })), _jsx("div", { className: "flex-1 overflow-y-auto p-4 space-y-4 pt-0 mt-3", children: !isFeatureEnabled ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full py-12 px-6 text-center", children: [_jsx("div", { className: "mb-4 p-4 bg-teal-50 dark:bg-teal-900/20 rounded-full", children: _jsx(ReceiptText, { className: "w-12 h-12 text-teal-500" }) }), _jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: "Expenses Feature" }), _jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4 max-w-sm", children: "Split costs fairly, track who owes whom, analyse expenses and settle balances easily. Perfect for shared trip expenses and group activities." }), isAdmin ? (_jsxs(Button, { onClick: onEnableFeature, className: "bg-gradient-to-r from-teal-500 to-cyan-600 hover:from-teal-600 hover:to-cyan-700 text-white", children: [_jsx(Unlock, { className: "w-4 h-4 mr-2" }), "Enable Feature"] })) : (_jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-500", children: [_jsx(Lock, { className: "w-4 h-4" }), "Ask admin to enable this feature"] }))] })) : 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" }) })) : !showSettleMode && expenses.length === 0 && !isAdding ? (_jsx("div", { className: "text-center py-8 text-gray-400 text-sm", children: "No expenses yet" })) : (_jsxs(_Fragment, { children: [!showSettleMode && isAdding && (_jsxs("div", { className: "bg-teal-50 dark:bg-teal-900/20 p-3 rounded-xl border border-teal-100 dark:border-teal-800 space-y-3", children: [_jsx(Input, { autoFocus: true, value: newTitle, onChange: (e) => 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 shadow-sm" }), _jsx("div", { className: "grid grid-cols-3 gap-2", children: _jsxs("div", { className: "col-span-2 relative", children: [_jsx("span", { className: "absolute left-2 top-2 text-gray-400 text-sm", children: "\u20B9" }), _jsx(Input, { type: "number", step: "0.01", value: newAmount, onChange: (e) => 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(Button, { onClick: () => setShowMoreOptions(!showMoreOptions), variant: "secondary", className: "w-full flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider", children: "More Options" }), _jsx(ChevronDown, { className: `w-4 h-4 text-gray-400 transition-transform ${showMoreOptions ? "rotate-180" : ""}` })] }), showMoreOptions && (_jsxs("div", { className: "bg-white dark:bg-gray-800 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider block mb-1", children: "Category" }), _jsx(Input, { placeholder: "Section (e.g., Day 1, Transport)", value: newCategory, onChange: (e) => setNewCategory(e.target.value), list: "section-suggestions", className: "text-sm" }), _jsx("datalist", { id: "section-suggestions", children: categories.map(category => (_jsx("option", { value: category }, category))) })] }), _jsx(SplitTypeSelector, { value: splitType, onChange: (type) => setSplitType(type) }), splitType !== "equal" && (_jsx(ParticipantSelector, { participants: splitParticipants, onChange: setSplitParticipants, onSplitsChange: setSplits, availableUsers: groupParticipants })), _jsx(SplitInputs, { splitType: splitType, totalAmount: parseFloat(newAmount) || 0, participants: splitParticipants, splits: splits, onChange: setSplits }), _jsxs("div", { children: [_jsx(Button, { onClick: handleGetLocation, disabled: gettingLocation || uploading, className: "w-full flex items-center justify-center gap-2 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 disabled:opacity-50 disabled:cursor-not-allowed border border-gray-200 dark:border-gray-600 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: "Add Location" })] })) }), newLocation && (_jsxs("div", { className: "mt-2 p-2 bg-white dark:bg-gray-700 rounded-lg border border-teal-200 dark:border-teal-800 flex items-center justify-between gap-2", children: [_jsxs("a", { href: newLocation, target: "_blank", rel: "noopener noreferrer", className: "text-xs text-blue-500 hover:underline truncate flex-1", children: [_jsx(MapPin, { className: "w-4 h-4 inline mr-1" }), "View Location"] }), _jsx("button", { onClick: () => setNewLocation(null), className: "text-red-500 hover:text-red-600", children: _jsx(X, { className: "w-4 h-4" }) })] }))] }), _jsxs("div", { children: [_jsxs("label", { className: "flex items-center gap-2 text-xs text-gray-500 cursor-pointer hover:text-teal-500 transition-colors bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-2", children: [_jsx(ImageIcon, { className: "w-4 h-4" }), receiptFile ? (_jsx("span", { className: "text-teal-500 truncate max-w-[150px]", children: receiptFile.name })) : ("Add Receipt"), _jsx(Input, { type: "file", className: "hidden", accept: "image/*", onChange: (e) => { var _a; return setReceiptFile(((_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0]) || null); } })] }), receiptFile && (_jsxs(Button, { onClick: () => setReceiptFile(null), className: "mt-2 w-full text-red-500 hover:text-red-600 text-xs flex items-center justify-center gap-2", variant: "ghost", children: [_jsx(Trash2, { className: "w-3 h-3" }), "Remove Receipt"] }))] }), _jsxs("div", { children: [_jsx("label", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider block mb-1", children: "Description" }), _jsx(Textarea, { value: newDescription, onChange: (e) => setNewDescription(e.target.value), placeholder: "Add details...", className: "w-full bg-white dark:bg-gray-700 border border-gray-200 dark:border-gray-600 rounded-lg p-2 text-xs text-gray-900 dark:text-white outline-none focus:border-teal-500 resize-none h-16" })] })] })), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: resetForm, 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" }), expenseId ? "Update" : "Add"] })] })] })), !showSettleMode &&
217
+ expenses.map((e) => {
218
+ var _a, _b, _c;
219
+ return e.title == "Internal_Settlement" ?
220
+ (_jsxs("div", { className: "w-full p-3 flex items-start gap-3 hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors group text-left", children: [_jsx("div", { className: "mt-1 p-2 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg text-teal-500 flex-shrink-0", children: _jsx(PiggyBankIcon, { className: "w-4 h-4" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("p", { className: "text-sm font-semibold text-gray-800 dark:text-gray-200 truncate", children: ["Paid to ", (_a = e.splits[0]) === null || _a === void 0 ? void 0 : _a.userId] }), _jsxs("p", { className: "text-sm font-bold text-gray-900 dark:text-white flex-shrink-0 ml-2", children: ["\u20B9", e.amount, " ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: e.currency })] })] }), _jsxs("div", { className: "text-[10px] text-gray-500 mt-1", children: ["by ", (_b = e.paidBy[0]) === null || _b === void 0 ? void 0 : _b.userId, " \u2022 ", new Date(e.date).toLocaleDateString()] })] })] }))
221
+ : (_jsxs("div", { className: "bg-gray-50 dark:bg-gray-800/30 border border-gray-100 dark:border-gray-800 rounded-xl transition-colors overflow-hidden", children: [_jsxs("button", { onClick: () => setExpandedExpenseId(expandedExpenseId === e._id ? null : e._id), className: "w-full p-3 flex items-start gap-3 hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors group text-left", 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, " ", _jsx("span", { className: "text-xs font-normal text-gray-500", children: e.currency })] })] }), _jsxs("div", { className: "text-[10px] text-gray-500 mt-1", children: ["by ", (_c = e.paidBy[0]) === null || _c === void 0 ? void 0 : _c.userId, " \u2022 ", new Date(e.date).toLocaleDateString()] })] }), _jsx(ChevronDown, { className: `w-4 h-4 text-gray-400 transition-transform flex-shrink-0 ${expandedExpenseId === e._id ? "rotate-180" : ""}` })] }), expandedExpenseId === e._id && (_jsxs("div", { className: "bg-white dark:bg-gray-700/50 border-t border-gray-200 dark:border-gray-700 p-3 space-y-3", children: [e.category && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: "Category" }), _jsx("p", { className: "text-xs text-gray-700 dark:text-gray-300", children: e.category })] })), (e.location || e.receipt) && _jsxs("div", { className: "flex flex-row w-full gap-2", children: [e.location && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: "Location" }), _jsxs("a", { href: e.location.startsWith("http")
222
+ ? e.location
223
+ : `https://maps.google.com/?q=${e.location}`, target: "_blank", rel: "noopener noreferrer", className: "text-xs text-blue-500 hover:underline flex items-center gap-1", children: [_jsx(MapPin, { className: "w-3 h-3" }), "View on Map"] })] })), e.receipt && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: "Receipt" }), _jsxs("a", { href: e.receipt, target: "_blank", rel: "noopener noreferrer", className: "text-xs text-blue-500 hover:underline flex items-center gap-1", children: [_jsx(Receipt, { className: "w-3 h-3" }), "View Receipt"] })] }))] }), e.description && (_jsxs("div", { children: [_jsx("p", { className: "text-[10px] font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-1", children: "Description" }), _jsx("p", { className: "text-xs text-gray-700 dark:text-gray-300 line-clamp-3", children: e.description })] })), _jsx(Button, { size: "sm", onClick: () => {
224
+ setSelectedExpenseForSplit(e);
225
+ setShowSplitModal(true);
226
+ }, className: "w-full text-teal-600 dark:text-teal-400 hover:text-teal-700 dark:hover:text-teal-300 text-xs border border-teal-200 dark:border-teal-800 bg-teal-50 dark:bg-teal-900/20", variant: "outline", children: "View Split Details" }), (e.createdBy === currentUserId || isAdmin) && (_jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { size: "sm", onClick: () => handleEdit(e), className: "flex-1 text-xs", variant: "outline", children: [_jsx(Pencil, { className: "w-3 h-3 mr-1" }), "Edit"] }), _jsxs(Button, { size: "sm", onClick: () => handleDelete(e._id), className: "flex-1 text-xs text-red-600 hover:text-red-700 border-red-200 dark:border-red-800 hover:bg-red-50 dark:hover:bg-red-900/20", variant: "outline", children: [_jsx(Trash2, { className: "w-3 h-3 mr-1" }), "Delete"] })] }))] }))] }, e._id));
227
+ })] })) }), isFeatureEnabled && !loading && (_jsx("div", { className: "p-4 border-t border-gray-200 dark:border-gray-700 flex-shrink-0 space-y-2", children: showSettleMode ? (_jsx(Button, { onClick: () => setShowSettleMode(false), 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", children: "Back to Expenses" })) : (_jsxs(_Fragment, { children: [!isAdding && (_jsxs(Button, { onClick: () => {
228
+ setExpenseId(null);
229
+ setIsAdding(true);
230
+ setSplitParticipants([]);
231
+ }, 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"] })), userOwesAmount() > 0 && !isAdding && (_jsxs(Button, { onClick: () => setShowSettleMode(true), variant: "outline", className: "w-full rounded-lg border-teal-500 text-teal-600 hover:bg-teal-50 dark:hover:bg-teal-900/10", children: [_jsx(CreditCard, { className: "w-4 h-4 mr-2" }), "Settle Debts (\u20B9", userOwesAmount().toFixed(2), ")"] }))] })) })), showSplitModal && selectedExpenseForSplit && (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-end z-50", children: _jsxs("div", { className: "w-full bg-white dark:bg-gray-900 rounded-t-2xl p-4 max-h-96 overflow-y-auto", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white", children: "Split Details" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
232
+ setShowSplitModal(false);
233
+ setSelectedExpenseForSplit(null);
234
+ }, children: _jsx(X, { className: "w-4 h-4" }) })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "bg-teal-50 dark:bg-teal-900/20 p-3 rounded-lg border border-teal-100 dark:border-teal-800", children: [_jsx("p", { className: "text-xs font-semibold text-teal-700 dark:text-teal-300 uppercase tracking-wider mb-2", children: selectedExpenseForSplit.title }), _jsxs("p", { className: "text-sm font-bold text-teal-900 dark:text-teal-100", children: ["\u20B9", selectedExpenseForSplit.amount, " ", selectedExpenseForSplit.currency] }), _jsxs("p", { className: "text-[10px] text-teal-600 dark:text-teal-400 mt-1", children: ["Split Type: ", _jsx("span", { className: "font-semibold capitalize", children: selectedExpenseForSplit.splitType })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wider", children: "Individual Splits" }), selectedExpenseForSplit.splits.map((split, idx) => (_jsxs("div", { className: "flex items-center justify-between p-2 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-100 dark:border-gray-700", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs font-medium text-gray-700 dark:text-gray-300", children: split.userId }), split.percentage && (_jsxs("p", { className: "text-[10px] text-gray-500", children: [split.percentage, "%"] })), split.shares && (_jsxs("p", { className: "text-[10px] text-gray-500", children: [split.shares, " shares"] }))] }), _jsxs("p", { className: "text-sm font-semibold text-gray-900 dark:text-white", children: ["\u20B9", split.amount.toFixed(2)] })] }, idx)))] })] })] }) })), _jsx(ExpenseAnalyticsDialog, { conversationId: conversationId, open: showAnalytics, onClose: () => setShowAnalytics(false) })] }));
235
+ }
@@ -0,0 +1,18 @@
1
+ import type { Message, Participant } from "@myrjfa/state/dist/lib/models/chat";
2
+ interface MessageBubbleProps {
3
+ message: Message;
4
+ participants: Participant[];
5
+ currentUserId: string;
6
+ showSender?: boolean;
7
+ senderProfilePic?: string;
8
+ onEdit?: (message: Message) => void;
9
+ onDelete?: (messageId: string) => void;
10
+ onReply?: (message: Message) => void;
11
+ onReactionAdd?: (messageId: string, emoji: string) => void;
12
+ onReactionRemove?: (messageId: string) => void;
13
+ onScrollToReply?: (messageId: string) => void;
14
+ onVote?: (pollId: string, optionIndex: number) => void;
15
+ }
16
+ export declare function MessageBubble({ message, participants, currentUserId, showSender, senderProfilePic, onEdit, onDelete, onReply, onReactionAdd, onReactionRemove, onScrollToReply, onVote }: MessageBubbleProps): import("react/jsx-runtime").JSX.Element;
17
+ export {};
18
+ //# 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,WAAW,EAAQ,MAAM,oCAAoC,CAAC;AASrF,UAAU,kBAAkB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,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,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1D;AAWD,wBAAgB,aAAa,CAAC,EAC1B,OAAO,EACP,YAAY,EACZ,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,QAAQ,EACR,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,MAAM,EACT,EAAE,kBAAkB,2CA0mBpB"}
@@ -0,0 +1,284 @@
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, useCallback, useMemo } from "react";
8
+ import EmojiPicker from 'emoji-picker-react';
9
+ import { chatApi } from "@myrjfa/state";
10
+ const QUICK_REACTIONS = [
11
+ { emoji: "❤️", label: "love" },
12
+ { emoji: "👍", label: "like" },
13
+ { emoji: "👎", label: "dislike" },
14
+ { emoji: "✅", label: "check" },
15
+ { emoji: "😠", label: "angry" },
16
+ { emoji: "😢", label: "sad" },
17
+ ];
18
+ export function MessageBubble({ message, participants, currentUserId, showSender, senderProfilePic, onEdit, onDelete, onReply, onReactionAdd, onReactionRemove, onScrollToReply, onVote }) {
19
+ var _a, _b, _c, _d, _e, _f;
20
+ const [showQuickReactions, setShowQuickReactions] = useState(false);
21
+ const [showReactionPicker, setShowReactionPicker] = useState(false);
22
+ const [showActionMenu, setShowActionMenu] = useState(false);
23
+ const [showMobileMenu, setShowMobileMenu] = useState(false);
24
+ const [poll, setPoll] = useState(null);
25
+ const reactionRef = useRef(null);
26
+ const actionRef = useRef(null);
27
+ const mobileMenuRef = useRef(null);
28
+ const mobilePopupRef = useRef(null);
29
+ const messageBubbleRef = useRef(null);
30
+ const isOwn = message.senderId == currentUserId;
31
+ useEffect(() => {
32
+ const pollId = message.content.referenceId;
33
+ if (message.type === "poll" && pollId) {
34
+ const fetchPoll = async () => {
35
+ setPoll(await chatApi.getPollById(pollId));
36
+ };
37
+ fetchPoll();
38
+ }
39
+ }, [message]);
40
+ // Close reaction picker when clicking outside
41
+ useEffect(() => {
42
+ const handleClickOutside = (event) => {
43
+ if (reactionRef.current && !reactionRef.current.contains(event.target)) {
44
+ setShowQuickReactions(false);
45
+ setShowReactionPicker(false);
46
+ }
47
+ };
48
+ if (showQuickReactions || showReactionPicker) {
49
+ document.addEventListener("mousedown", handleClickOutside);
50
+ return () => {
51
+ document.removeEventListener("mousedown", handleClickOutside);
52
+ };
53
+ }
54
+ }, [showQuickReactions, showReactionPicker]);
55
+ // Close mobile menu when clicking outside
56
+ useEffect(() => {
57
+ const handleClickOutside = (event) => {
58
+ if (mobileMenuRef.current && !mobileMenuRef.current.contains(event.target)) {
59
+ setShowMobileMenu(false);
60
+ }
61
+ };
62
+ if (showMobileMenu) {
63
+ document.addEventListener("mousedown", handleClickOutside);
64
+ return () => {
65
+ document.removeEventListener("mousedown", handleClickOutside);
66
+ };
67
+ }
68
+ }, [showMobileMenu]);
69
+ // Position popup to stay within viewport and over message
70
+ useEffect(() => {
71
+ if (showMobileMenu && mobilePopupRef.current && messageBubbleRef.current) {
72
+ const popup = mobilePopupRef.current;
73
+ const messageBubble = messageBubbleRef.current;
74
+ const bubbleRect = messageBubble.getBoundingClientRect();
75
+ const popupRect = popup.getBoundingClientRect();
76
+ const viewportHeight = window.innerHeight;
77
+ const viewportWidth = window.innerWidth;
78
+ const padding = 8;
79
+ // Reset styles
80
+ popup.style.top = "auto";
81
+ popup.style.bottom = "auto";
82
+ popup.style.left = "auto";
83
+ popup.style.right = "auto";
84
+ popup.style.transform = "none";
85
+ // Position horizontally - prefer the side with more space
86
+ if (isOwn) {
87
+ // Message is on right, try to position popup on left of message
88
+ const spaceOnLeft = bubbleRect.left;
89
+ const popupWidth = 256; // w-64 = 16rem = 256px
90
+ if (spaceOnLeft > popupWidth + padding) {
91
+ // Enough space on left
92
+ popup.style.right = "100%";
93
+ popup.style.marginRight = "8px";
94
+ }
95
+ else {
96
+ // Not enough space, position on right but keep within viewport
97
+ popup.style.left = "100%";
98
+ popup.style.marginLeft = "8px";
99
+ // If it goes out of right edge, adjust
100
+ const rect = popup.getBoundingClientRect();
101
+ if (rect.right > viewportWidth - padding) {
102
+ popup.style.left = "auto";
103
+ popup.style.right = "0";
104
+ }
105
+ }
106
+ }
107
+ else {
108
+ // Message is on left, try to position popup on right of message
109
+ const spaceOnRight = viewportWidth - bubbleRect.right;
110
+ const popupWidth = 256; // w-64 = 16rem = 256px
111
+ if (spaceOnRight > popupWidth + padding) {
112
+ // Enough space on right
113
+ popup.style.left = "100%";
114
+ popup.style.marginLeft = "8px";
115
+ }
116
+ else {
117
+ // Not enough space, position on left but keep within viewport
118
+ popup.style.right = "100%";
119
+ popup.style.marginRight = "8px";
120
+ // If it goes out of left edge, adjust
121
+ const rect = popup.getBoundingClientRect();
122
+ if (rect.left < padding) {
123
+ popup.style.right = "auto";
124
+ popup.style.left = "0";
125
+ }
126
+ }
127
+ }
128
+ // Position vertically - center on message
129
+ popup.style.top = "50%";
130
+ popup.style.transform = "translateY(-50%)";
131
+ // Make sure it doesn't go out of bottom
132
+ setTimeout(() => {
133
+ const rect = popup.getBoundingClientRect();
134
+ if (rect.bottom > viewportHeight - padding) {
135
+ popup.style.top = "auto";
136
+ popup.style.bottom = "0";
137
+ popup.style.transform = "translateY(0)";
138
+ }
139
+ else if (rect.top < padding) {
140
+ popup.style.top = "0";
141
+ popup.style.bottom = "auto";
142
+ popup.style.transform = "translateY(0)";
143
+ }
144
+ }, 0);
145
+ }
146
+ }, [showMobileMenu, isOwn]);
147
+ if (message.type === "system" || message.type === "milestone" || message.type === "expense") {
148
+ 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 }) }));
149
+ }
150
+ const time = new Date(message.createdAt).toLocaleTimeString([], {
151
+ hour: "2-digit",
152
+ minute: "2-digit",
153
+ });
154
+ const createMentionRegex = useCallback((participants) => {
155
+ const userIds = participants.map(p => p.userId);
156
+ // Escape special regex characters in usernames to ensure they are matched literally
157
+ const escapedUserIds = userIds.map(id => id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
158
+ // Sort by length descending to match longer names first, to prevent partial matches
159
+ escapedUserIds.sort((a, b) => b.length - a.length);
160
+ if (escapedUserIds.length === 0) {
161
+ return null;
162
+ }
163
+ // Match '@' followed by an exact username, then a non-word character or end of string.
164
+ // The `(?=\\s|$|[,.!?;:])` part ensures the mention is followed by a space, end of string, or common punctuation.
165
+ // This regex is designed to be as specific as possible to avoid false positives while allowing spaces/symbols.
166
+ const pattern = `@(${escapedUserIds.join('|')})(?=\\s|$|[,.!?;:])`;
167
+ return new RegExp(pattern, 'gi');
168
+ }, []);
169
+ const mentionRegex = useMemo(() => createMentionRegex(participants), [participants, createMentionRegex]);
170
+ const renderTextWithLinks = useCallback((text) => {
171
+ const elements = [];
172
+ let lastIndex = 0;
173
+ // Store all matches with their type and original value
174
+ const allMatches = [];
175
+ // Define regexes inside useCallback to avoid re-creation on every render if they don't depend on props
176
+ const linkRegex = /(https?:\/\/[^\s]+)/gi; // Added 'i' flag for case-insensitivity if needed, though URLs are usually case-sensitive for hostnames
177
+ const emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)/gi; // Added 'i' flag
178
+ const phoneRegex = /(\+?1?\d{9,15})/g; // Phone numbers are typically case-insensitive
179
+ // Find links
180
+ for (const match of text.matchAll(linkRegex)) {
181
+ if (match.index !== undefined) {
182
+ allMatches.push({ index: match.index, length: match[0].length, type: "link", value: match[0] });
183
+ }
184
+ }
185
+ // Find emails
186
+ for (const match of text.matchAll(emailRegex)) {
187
+ if (match.index !== undefined) {
188
+ allMatches.push({ index: match.index, length: match[0].length, type: "email", value: match[0] });
189
+ }
190
+ }
191
+ // Find phones
192
+ for (const match of text.matchAll(phoneRegex)) {
193
+ if (match.index !== undefined) {
194
+ allMatches.push({ index: match.index, length: match[0].length, type: "phone", value: match[0] });
195
+ }
196
+ }
197
+ // Find mentions
198
+ if (mentionRegex) {
199
+ for (const match of text.matchAll(mentionRegex)) {
200
+ if (match.index !== undefined) {
201
+ allMatches.push({ index: match.index, length: match[0].length, type: "mention", value: match[0] });
202
+ }
203
+ }
204
+ }
205
+ // Sort all matches by their starting index
206
+ allMatches.sort((a, b) => a.index - b.index);
207
+ for (const match of allMatches) {
208
+ // Add preceding text
209
+ if (match.index > lastIndex) {
210
+ elements.push(text.substring(lastIndex, match.index));
211
+ }
212
+ // Add the matched element
213
+ if (match.type === "link") {
214
+ elements.push(_jsx("a", { href: match.value, target: "_blank", rel: "noopener noreferrer", className: "underline hover:opacity-80 break-all text-indigo-500", children: match.value }, `link-${match.index}`));
215
+ }
216
+ else if (match.type === "email") {
217
+ elements.push(_jsx("a", { href: `mailto:${match.value}`, className: "underline hover:opacity-80 break-all text-indigo-500", children: match.value }, `email-${match.index}`));
218
+ }
219
+ else if (match.type === "phone") {
220
+ elements.push(_jsx("a", { href: `tel:${match.value}`, className: "underline hover:opacity-80 break-all text-indigo-200", children: match.value }, `phone-${match.index}`));
221
+ }
222
+ else if (match.type === "mention") {
223
+ // The regex already ensures it's a valid participant userId
224
+ elements.push(_jsx("a", { href: `/chat?u=${match.value.substring(1)}`, className: "underline hover:opacity-80 break-all text-indigo-200", children: match.value }, `mention-${match.index}`));
225
+ }
226
+ lastIndex = match.index + match.length;
227
+ }
228
+ // Add any remaining text
229
+ if (lastIndex < text.length) {
230
+ elements.push(text.substring(lastIndex));
231
+ }
232
+ return elements;
233
+ }, [mentionRegex]);
234
+ return (_jsxs("div", { id: `message-${message._id}`, className: `flex mb-2 ${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 ${isOwn ? "flex-row-reverse" : "flex-row"}`, children: [_jsxs("div", { className: `max-w-xs sm:max-w-md md:max-w-lg flex flex-col ${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: () => 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 ${isOwn
235
+ ? "bg-opacity-50 bg-teal-600/40 border-teal-300 text-teal-100"
236
+ : "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 ", (_a = message.replyTo) === null || _a === void 0 ? void 0 : _a.senderId] }), _jsx("div", { className: "truncate text-gray-500 dark:text-gray-400 text-xs", children: ((_d = (_c = (_b = message.replyTo) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.substring(0, 50)) || "Media/Attachment" })] })), _jsxs("div", { className: `px-4 py-2.5 rounded-2xl relative shadow-sm backdrop-blur-md
237
+ ${isOwn
238
+ ? "bg-gradient-to-r from-teal-500/70 to-teal-600/70 text-white rounded-br-md border border-teal-400/30"
239
+ : "bg-white dark:bg-gray-800 text-gray-900 dark:text-white rounded-bl-md border border-gray-100 dark:border-gray-700"}
240
+ ${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((m, i) => (_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" })) : m.type === "audio" ?
241
+ (_jsx("audio", { src: m.url, controls: true, children: _jsxs("a", { href: m.url, className: "text-sm underline", target: "_blank", rel: "noopener noreferrer", children: ["\uD83D\uDCCE ", m.name] }) }))
242
+ : (_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 : "") }), poll && (_jsxs("div", { className: "mt-3 space-y-2 min-w-[200px]", children: [poll.options.map((opt, i) => {
243
+ const votes = poll.votes || [];
244
+ const optionVotes = votes.filter((v) => v.optionIndex === i);
245
+ const hasVoted = optionVotes.some((v) => v.userId === currentUserId);
246
+ const percentage = votes.length > 0 ? (optionVotes.length / votes.length) * 100 : 0;
247
+ return (_jsxs("div", { className: `relative overflow-hidden rounded-lg border p-2 cursor-pointer transition-all active:scale-[0.98] ${hasVoted
248
+ ? "border-indigo-500 bg-indigo-50/50 dark:bg-indigo-900/30"
249
+ : "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600 bg-indigo-50/50 dark:bg-indigo-900/30"}
250
+ }`, onClick: () => onVote === null || onVote === void 0 ? void 0 : onVote(poll._id, i), children: [_jsx("div", { className: "absolute inset-0 bg-indigo-400/10 pointer-events-none transition-all duration-500", style: { width: `${percentage}%` } }), _jsxs("div", { className: "relative flex justify-between items-center text-sm", children: [_jsx("span", { className: `${hasVoted ? "font-semibold text-indigo-700 dark:text-indigo-300" : "text-gray-700 dark:text-gray-200"}`, children: opt.text }), _jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: optionVotes.length > 0 && `${optionVotes.length} (${Math.round(percentage)}%)` })] })] }, i));
251
+ }), _jsxs("div", { className: "text-[10px] text-gray-400 dark:text-gray-500 mt-1", children: [((_f = poll.votes) === null || _f === void 0 ? void 0 : _f.length) || 0, " votes \u2022 ", poll.allowMultiple ? "Multiple choice" : "Single choice"] })] })), _jsxs("div", { className: `flex items-center justify-end gap-1 mt-1 ${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 ${isOwn ? "justify-end" : "justify-start"} px-2`, children: Object.entries(message.reactions.reduce((acc, r) => {
252
+ acc[r.emoji] = (acc[r.emoji] || 0) + 1;
253
+ return acc;
254
+ }, {})).map(([emoji, count]) => (_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", onClick: () => {
255
+ if (message.reactions.find(r => r.emoji == emoji && r.userId == currentUserId)) {
256
+ onReactionRemove === null || onReactionRemove === void 0 ? void 0 : onReactionRemove(message._id);
257
+ }
258
+ }, children: [emoji, " ", count > 1 && count] }, emoji))) }))] }), !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: () => {
259
+ setShowQuickReactions(!showQuickReactions);
260
+ setShowReactionPicker(false);
261
+ }, 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 ${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((reaction) => (_jsx("button", { onClick: () => {
262
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
263
+ setShowQuickReactions(false);
264
+ }, 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: () => {
265
+ setShowQuickReactions(false);
266
+ setShowReactionPicker(true);
267
+ }, 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: (emojiObject) => {
268
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, emojiObject.emoji);
269
+ setShowReactionPicker(false);
270
+ setShowQuickReactions(false);
271
+ } }) }))] }) }))] }), _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: () => onReply === null || onReply === void 0 ? void 0 : onReply(message), children: [_jsx(Reply, { className: "w-4 h-4 mr-2" }), "Reply"] }), isOwn && _jsxs(DropdownMenuItem, { onClick: () => onEdit === null || onEdit === void 0 ? void 0 : onEdit(message), children: [_jsx(PencilIcon, { className: "w-4 h-4 mr-2" }), "Edit"] }), isOwn && _jsxs(DropdownMenuItem, { className: "text-red-500", onClick: () => 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: () => 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: () => 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((reaction) => (_jsx("button", { onClick: () => {
272
+ onReactionAdd === null || onReactionAdd === void 0 ? void 0 : onReactionAdd(message._id, reaction.emoji);
273
+ setShowMobileMenu(false);
274
+ }, 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: () => {
275
+ onReply === null || onReply === void 0 ? void 0 : onReply(message);
276
+ setShowMobileMenu(false);
277
+ }, 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: () => {
278
+ onEdit === null || onEdit === void 0 ? void 0 : onEdit(message);
279
+ setShowMobileMenu(false);
280
+ }, 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: () => {
281
+ onDelete === null || onDelete === void 0 ? void 0 : onDelete(message._id);
282
+ setShowMobileMenu(false);
283
+ }, 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"] })] }))] }) }))] })] }))] })] }));
284
+ }
@@ -0,0 +1,21 @@
1
+ import { Message } from "@myrjfa/state";
2
+ interface MessageInputProps {
3
+ onSend: (text: string, files?: File[]) => void;
4
+ onTypingStart?: () => void;
5
+ onTypingStop?: () => void;
6
+ disabled?: boolean;
7
+ replyTo?: any;
8
+ editMessage?: Message | null;
9
+ onEdit?: (messageid: string, text: string) => void;
10
+ onCancelEdit?: () => void;
11
+ onCancelReply?: () => void;
12
+ onAddMilestone?: () => void;
13
+ onAddExpense?: () => void;
14
+ onCreatePoll?: (question: string, options: string[], allowMultiple: boolean) => void;
15
+ participants?: any[];
16
+ activePanel?: "chat" | "milestones" | "expenses";
17
+ }
18
+ export declare function MessageInput({ onSend, onTypingStart, onTypingStop, disabled, replyTo, editMessage, onEdit, onCancelEdit, onCancelReply, onAddMilestone, onAddExpense, onCreatePoll, participants, // Added
19
+ activePanel }: MessageInputProps): import("react/jsx-runtime").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=MessageInput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageInput.d.ts","sourceRoot":"","sources":["../../src/chat/MessageInput.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,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,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,OAAO,KAAK,IAAI,CAAC;IACrF,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,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,WAAW,EACX,MAAM,EACN,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAiB,EAAE,QAAQ;AAC3B,WAAoB,EACvB,EAAE,iBAAiB,2CAobnB"}