@wealthx/shadcn 1.5.23 → 1.5.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +147 -147
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-K4GJTP6N.mjs → chunk-4FJC64FV.mjs} +467 -386
- package/dist/{chunk-FGHM34AV.mjs → chunk-7JVKSZ4O.mjs} +1 -1
- package/dist/{chunk-6HTE24TP.mjs → chunk-EY5YPFKX.mjs} +1 -1
- package/dist/{chunk-3HFOSFOM.mjs → chunk-IG7DEIWU.mjs} +4 -4
- package/dist/{chunk-2VTOF7PW.mjs → chunk-K2KX3NX7.mjs} +1 -1
- package/dist/{chunk-NXZ2F4JA.mjs → chunk-KGBLORCQ.mjs} +1 -1
- package/dist/{chunk-2SDEURIQ.mjs → chunk-PX2B3Q3A.mjs} +7 -7
- package/dist/{chunk-66NM4AX2.mjs → chunk-QM7LU2BR.mjs} +10 -10
- package/dist/{chunk-4UCRTTVL.mjs → chunk-RNLIZRAK.mjs} +1 -1
- package/dist/{chunk-BWG7AX6X.mjs → chunk-SQ54W3JH.mjs} +1 -1
- package/dist/{chunk-RJHE3V4M.mjs → chunk-UMR3HVZF.mjs} +4 -4
- package/dist/{chunk-JGUC3KCA.mjs → chunk-VNB5E7SI.mjs} +5 -5
- package/dist/{chunk-ETT5JAXF.mjs → chunk-Z5QI7CW2.mjs} +1 -1
- package/dist/components/ui/about-you-form.mjs +4 -4
- package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
- package/dist/components/ui/{ai-conversations.js → ai-conversations/index.js} +818 -740
- package/dist/components/ui/ai-conversations/index.mjs +42 -0
- package/dist/components/ui/appointment-action-dialogs.mjs +1 -1
- package/dist/components/ui/appointment-availability-settings.mjs +5 -5
- package/dist/components/ui/appointment-book-dialog.mjs +4 -4
- package/dist/components/ui/appointment-detail-sheet.mjs +3 -3
- package/dist/components/ui/appointment-upcoming-card.mjs +1 -1
- package/dist/components/ui/assets-liabilities-side-card.mjs +6 -6
- package/dist/components/ui/backoffice-signup-steps.mjs +4 -4
- package/dist/components/ui/bank-statement-generate-dialog.mjs +6 -6
- package/dist/components/ui/contact-alert-dialog/index.mjs +2 -2
- package/dist/components/ui/create-contact-modal.mjs +3 -3
- package/dist/components/ui/date-picker.mjs +2 -2
- package/dist/components/ui/expense-detail-item.mjs +3 -3
- package/dist/components/ui/expense-work-details.mjs +9 -9
- package/dist/components/ui/file-preview-dialog.mjs +2 -2
- package/dist/components/ui/financial-drawers.mjs +2 -2
- package/dist/components/ui/form-primitives.mjs +2 -2
- package/dist/components/ui/frontend-signup-steps.mjs +5 -5
- package/dist/components/ui/income-work-details.mjs +3 -3
- package/dist/components/ui/kanban-column.mjs +4 -4
- package/dist/components/ui/opportunity-card.mjs +1 -1
- package/dist/components/ui/opportunity-edit-modals.mjs +6 -6
- package/dist/components/ui/opportunity-summary-tab.mjs +8 -8
- package/dist/components/ui/pipeline-board.mjs +6 -6
- package/dist/components/ui/pipeline-dialogs.mjs +5 -5
- package/dist/components/ui/property-report-dialog.mjs +4 -4
- package/dist/components/ui/savings-goal-modal.mjs +4 -4
- package/dist/components/ui/share-details-dialog.mjs +2 -2
- package/dist/components/ui/signup-form-primitives.mjs +1 -1
- package/dist/index.js +4912 -4834
- package/dist/index.mjs +160 -160
- package/dist/styles.css +1 -1
- package/package.json +4 -4
- package/src/components/index.tsx +1 -0
- package/src/components/ui/ai-conversations/helpers.tsx +31 -0
- package/src/components/ui/ai-conversations/index.tsx +468 -0
- package/src/components/ui/ai-conversations/lead-panel.tsx +517 -0
- package/src/components/ui/ai-conversations/list.tsx +335 -0
- package/src/components/ui/ai-conversations/thread.tsx +919 -0
- package/src/components/ui/ai-conversations/types.ts +83 -0
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +1 -1
- package/dist/components/ui/ai-conversations.mjs +0 -42
- package/src/components/ui/ai-conversations.tsx +0 -2213
- package/dist/{chunk-A43XIVO6.mjs → chunk-2PWTXE66.mjs} +3 -3
- package/dist/{chunk-PSBQ4I3M.mjs → chunk-53ZB2JWT.mjs} +6 -6
- package/dist/{chunk-G6RCC2SF.mjs → chunk-5ST6BK7R.mjs} +3 -3
- package/dist/{chunk-H5NI6ZIU.mjs → chunk-734FOOJC.mjs} +3 -3
- package/dist/{chunk-ONYADWSO.mjs → chunk-ABVCQWDY.mjs} +3 -3
- package/dist/{chunk-AADJ5IT6.mjs → chunk-ECC2LLZM.mjs} +3 -3
- package/dist/{chunk-DK55HZPN.mjs → chunk-KENLXFJ7.mjs} +6 -6
- package/dist/{chunk-ET4MTPIY.mjs → chunk-LDC6V6DJ.mjs} +3 -3
- package/dist/{chunk-3S4XQTAL.mjs → chunk-SO4RB3XB.mjs} +9 -9
- package/dist/{chunk-UMF6LLQK.mjs → chunk-TRM3KIHT.mjs} +3 -3
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Flag, Mail, MessageSquare, Search } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Badge } from "@/components/ui/badge";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
8
|
+
import type {
|
|
9
|
+
AiConvChannelFilter,
|
|
10
|
+
AiConvFilterTab,
|
|
11
|
+
AiConvListItemData,
|
|
12
|
+
AiConvStatus,
|
|
13
|
+
} from "./types";
|
|
14
|
+
import {
|
|
15
|
+
ContactAvatar,
|
|
16
|
+
displayContactName,
|
|
17
|
+
PANEL_HEADER_HEIGHT,
|
|
18
|
+
} from "./helpers.tsx";
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// ConversationStatusChip
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
type BadgeVariant = "success" | "default" | "warning" | "secondary";
|
|
25
|
+
|
|
26
|
+
const STATUS_CONFIG: Record<
|
|
27
|
+
AiConvStatus,
|
|
28
|
+
{ label: string; variant: BadgeVariant; dotClass: string }
|
|
29
|
+
> = {
|
|
30
|
+
"ai-active": {
|
|
31
|
+
label: "AI Active",
|
|
32
|
+
variant: "success",
|
|
33
|
+
dotClass: "bg-success",
|
|
34
|
+
},
|
|
35
|
+
manual: {
|
|
36
|
+
label: "Manual",
|
|
37
|
+
variant: "default",
|
|
38
|
+
dotClass: "bg-primary",
|
|
39
|
+
},
|
|
40
|
+
"needs-attention": {
|
|
41
|
+
label: "Needs Attention",
|
|
42
|
+
variant: "warning",
|
|
43
|
+
dotClass: "bg-warning",
|
|
44
|
+
},
|
|
45
|
+
closed: {
|
|
46
|
+
label: "Closed",
|
|
47
|
+
variant: "secondary",
|
|
48
|
+
dotClass: "bg-muted-foreground/50",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export interface ConversationStatusChipProps {
|
|
53
|
+
status: AiConvStatus;
|
|
54
|
+
showDot?: boolean;
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function ConversationStatusChip({
|
|
59
|
+
status,
|
|
60
|
+
showDot = false,
|
|
61
|
+
className,
|
|
62
|
+
}: ConversationStatusChipProps) {
|
|
63
|
+
const { label, variant, dotClass } = STATUS_CONFIG[status];
|
|
64
|
+
return (
|
|
65
|
+
<Badge variant={variant} className={className}>
|
|
66
|
+
{showDot && (
|
|
67
|
+
<span className={cn("size-1.5 shrink-0 rounded-full", dotClass)} />
|
|
68
|
+
)}
|
|
69
|
+
{label}
|
|
70
|
+
</Badge>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// ConversationListItem
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
export interface ConversationListItemProps {
|
|
79
|
+
data: AiConvListItemData;
|
|
80
|
+
isActive?: boolean;
|
|
81
|
+
onClick?: (id: string) => void;
|
|
82
|
+
onRead?: (id: string) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function ConversationListItem({
|
|
86
|
+
data,
|
|
87
|
+
isActive,
|
|
88
|
+
onClick,
|
|
89
|
+
onRead,
|
|
90
|
+
}: ConversationListItemProps) {
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => {
|
|
95
|
+
onClick?.(data.id);
|
|
96
|
+
onRead?.(data.id);
|
|
97
|
+
}}
|
|
98
|
+
className={cn(
|
|
99
|
+
"w-full flex items-start gap-3 px-3 py-3 text-left transition-colors",
|
|
100
|
+
"border-b border-border last:border-b-0",
|
|
101
|
+
isActive ? "bg-muted" : "hover:bg-muted/40",
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<ContactAvatar name={data.contact.name} />
|
|
105
|
+
<div className="min-w-0 flex-1">
|
|
106
|
+
{/* Row 1 — name + icons + timestamp */}
|
|
107
|
+
<div className="mb-1 flex items-center justify-between gap-2">
|
|
108
|
+
<span className="truncate text-sm font-semibold text-foreground">
|
|
109
|
+
{displayContactName(data.contact.name)}
|
|
110
|
+
</span>
|
|
111
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
112
|
+
{data.status === "needs-attention" && (
|
|
113
|
+
<Flag className="size-3 text-warning-text" />
|
|
114
|
+
)}
|
|
115
|
+
{data.channel === "email" ? (
|
|
116
|
+
<Mail className="size-3 text-muted-foreground" />
|
|
117
|
+
) : (
|
|
118
|
+
<MessageSquare className="size-3 text-muted-foreground" />
|
|
119
|
+
)}
|
|
120
|
+
<span className="whitespace-nowrap text-xs text-muted-foreground">
|
|
121
|
+
{data.timestamp}
|
|
122
|
+
</span>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
{/* Row 2 — status chip + assigned advisor + unread badge */}
|
|
126
|
+
<div className="mb-1.5 flex items-center justify-between gap-2">
|
|
127
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
128
|
+
<ConversationStatusChip status={data.status} showDot />
|
|
129
|
+
{data.assignedTo && (
|
|
130
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
131
|
+
→ {data.assignedTo}
|
|
132
|
+
</span>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
{data.unreadCount ? (
|
|
136
|
+
<Badge
|
|
137
|
+
variant="default"
|
|
138
|
+
className="size-4 shrink-0 justify-center px-0 text-[10px]"
|
|
139
|
+
>
|
|
140
|
+
{data.unreadCount}
|
|
141
|
+
</Badge>
|
|
142
|
+
) : null}
|
|
143
|
+
</div>
|
|
144
|
+
{/* Row 3 — last message preview */}
|
|
145
|
+
<p className="line-clamp-2 text-sm leading-relaxed text-muted-foreground">
|
|
146
|
+
{data.lastMessageRole === "bot" ? (
|
|
147
|
+
<span className="font-medium text-foreground/60">AI: </span>
|
|
148
|
+
) : data.lastMessageRole === "advisor" ? (
|
|
149
|
+
<span className="font-medium text-foreground/60">You: </span>
|
|
150
|
+
) : data.lastMessageRole === "visitor" ? (
|
|
151
|
+
<span className="font-medium text-foreground/60">
|
|
152
|
+
{displayContactName(data.contact.name).split(" ")[0]}:{" "}
|
|
153
|
+
</span>
|
|
154
|
+
) : null}
|
|
155
|
+
{data.lastMessage}
|
|
156
|
+
</p>
|
|
157
|
+
</div>
|
|
158
|
+
</button>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// ConversationList
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
function filterConversations(
|
|
167
|
+
conversations: AiConvListItemData[],
|
|
168
|
+
query: string,
|
|
169
|
+
filter: AiConvFilterTab,
|
|
170
|
+
channelFilter: AiConvChannelFilter,
|
|
171
|
+
): AiConvListItemData[] {
|
|
172
|
+
const q = query.toLowerCase();
|
|
173
|
+
return conversations.filter((c) => {
|
|
174
|
+
const matchesFilter =
|
|
175
|
+
filter === "all" ||
|
|
176
|
+
(filter === "emails"
|
|
177
|
+
? (c.channel ?? "chat") === "email"
|
|
178
|
+
: c.status === filter);
|
|
179
|
+
const matchesChannel =
|
|
180
|
+
channelFilter === "all" || (c.channel ?? "chat") === channelFilter;
|
|
181
|
+
const matchesSearch =
|
|
182
|
+
!q ||
|
|
183
|
+
c.contact.name.toLowerCase().includes(q) ||
|
|
184
|
+
c.lastMessage.toLowerCase().includes(q);
|
|
185
|
+
return matchesFilter && matchesChannel && matchesSearch;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const FILTER_TABS: { id: AiConvFilterTab; label: string }[] = [
|
|
190
|
+
{ id: "all", label: "All" },
|
|
191
|
+
{ id: "emails", label: "Emails" },
|
|
192
|
+
{ id: "needs-attention", label: "Urgent" },
|
|
193
|
+
{ id: "closed", label: "Archived" },
|
|
194
|
+
{ id: "ai-active", label: "AI Active" },
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
export interface ConversationListProps {
|
|
198
|
+
conversations: AiConvListItemData[];
|
|
199
|
+
activeId?: string;
|
|
200
|
+
searchQuery?: string;
|
|
201
|
+
activeFilter?: AiConvFilterTab;
|
|
202
|
+
channelFilter?: AiConvChannelFilter;
|
|
203
|
+
hasMore?: boolean;
|
|
204
|
+
isLoadingMore?: boolean;
|
|
205
|
+
onSearchChange?: (v: string) => void;
|
|
206
|
+
onFilterChange?: (f: AiConvFilterTab) => void;
|
|
207
|
+
onChannelFilterChange?: (f: AiConvChannelFilter) => void;
|
|
208
|
+
onSelect?: (id: string) => void;
|
|
209
|
+
onRead?: (id: string) => void;
|
|
210
|
+
onLoadMore?: () => void;
|
|
211
|
+
className?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function ConversationList({
|
|
215
|
+
conversations,
|
|
216
|
+
activeId,
|
|
217
|
+
searchQuery = "",
|
|
218
|
+
activeFilter = "all",
|
|
219
|
+
channelFilter = "all",
|
|
220
|
+
hasMore,
|
|
221
|
+
isLoadingMore,
|
|
222
|
+
onSearchChange,
|
|
223
|
+
onFilterChange,
|
|
224
|
+
onSelect,
|
|
225
|
+
onRead,
|
|
226
|
+
onLoadMore,
|
|
227
|
+
className,
|
|
228
|
+
}: ConversationListProps) {
|
|
229
|
+
return (
|
|
230
|
+
<div
|
|
231
|
+
className={cn(
|
|
232
|
+
"flex flex-col border-r border-border bg-background",
|
|
233
|
+
className,
|
|
234
|
+
)}
|
|
235
|
+
>
|
|
236
|
+
<div className={cn(PANEL_HEADER_HEIGHT, "flex shrink-0 flex-col")}>
|
|
237
|
+
<div className="flex shrink-0 items-center border-b border-border px-3 py-2.5">
|
|
238
|
+
<div className="relative flex-1">
|
|
239
|
+
<Search className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
|
|
240
|
+
<Input
|
|
241
|
+
value={searchQuery}
|
|
242
|
+
onChange={(e) => onSearchChange?.(e.target.value)}
|
|
243
|
+
placeholder="Search conversations..."
|
|
244
|
+
className="h-8 pl-8 text-sm"
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div className="flex flex-1 items-center border-b border-border">
|
|
250
|
+
<Tabs
|
|
251
|
+
value={activeFilter}
|
|
252
|
+
onValueChange={(v) => v && onFilterChange?.(v as AiConvFilterTab)}
|
|
253
|
+
className="w-full"
|
|
254
|
+
>
|
|
255
|
+
<TabsList
|
|
256
|
+
variant="line"
|
|
257
|
+
className="w-full justify-start gap-0 h-auto"
|
|
258
|
+
>
|
|
259
|
+
{FILTER_TABS.map((tab) => (
|
|
260
|
+
<TabsTrigger
|
|
261
|
+
key={tab.id}
|
|
262
|
+
value={tab.id}
|
|
263
|
+
className="flex-none px-3 py-2 text-xs"
|
|
264
|
+
>
|
|
265
|
+
{tab.label}
|
|
266
|
+
</TabsTrigger>
|
|
267
|
+
))}
|
|
268
|
+
</TabsList>
|
|
269
|
+
</Tabs>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{/* List */}
|
|
274
|
+
<div className="flex-1 overflow-y-auto" tabIndex={0}>
|
|
275
|
+
{(() => {
|
|
276
|
+
const filtered = filterConversations(
|
|
277
|
+
conversations,
|
|
278
|
+
searchQuery,
|
|
279
|
+
activeFilter,
|
|
280
|
+
channelFilter,
|
|
281
|
+
);
|
|
282
|
+
return filtered.length === 0 ? (
|
|
283
|
+
<div className="flex flex-col items-center justify-center gap-2 p-8 text-muted-foreground">
|
|
284
|
+
<MessageSquare className="size-8 opacity-30" />
|
|
285
|
+
<p className="text-sm">No conversations</p>
|
|
286
|
+
{searchQuery && (
|
|
287
|
+
<Button
|
|
288
|
+
variant="outline"
|
|
289
|
+
size="sm"
|
|
290
|
+
onClick={() => onSearchChange?.("")}
|
|
291
|
+
>
|
|
292
|
+
Clear search
|
|
293
|
+
</Button>
|
|
294
|
+
)}
|
|
295
|
+
{!searchQuery && activeFilter !== "all" && (
|
|
296
|
+
<Button
|
|
297
|
+
variant="outline"
|
|
298
|
+
size="sm"
|
|
299
|
+
onClick={() => onFilterChange?.("all")}
|
|
300
|
+
>
|
|
301
|
+
Clear filter
|
|
302
|
+
</Button>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
) : (
|
|
306
|
+
<>
|
|
307
|
+
{filtered.map((item) => (
|
|
308
|
+
<ConversationListItem
|
|
309
|
+
key={item.id}
|
|
310
|
+
data={item}
|
|
311
|
+
isActive={activeId === item.id}
|
|
312
|
+
onClick={onSelect}
|
|
313
|
+
onRead={onRead}
|
|
314
|
+
/>
|
|
315
|
+
))}
|
|
316
|
+
{hasMore && (
|
|
317
|
+
<div className="border-t border-border p-3">
|
|
318
|
+
<Button
|
|
319
|
+
variant="outline"
|
|
320
|
+
size="sm"
|
|
321
|
+
className="w-full"
|
|
322
|
+
disabled={isLoadingMore}
|
|
323
|
+
onClick={onLoadMore}
|
|
324
|
+
>
|
|
325
|
+
{isLoadingMore ? "Loading..." : "Load more"}
|
|
326
|
+
</Button>
|
|
327
|
+
</div>
|
|
328
|
+
)}
|
|
329
|
+
</>
|
|
330
|
+
);
|
|
331
|
+
})()}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
);
|
|
335
|
+
}
|