@algenium/blocks 1.1.2-rc.1 → 1.2.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -14,6 +14,8 @@ var DialogPrimitive = require('@radix-ui/react-dialog');
14
14
  var vaul = require('vaul');
15
15
  var PopoverPrimitive = require('@radix-ui/react-popover');
16
16
  var ScrollAreaPrimitive = require('@radix-ui/react-scroll-area');
17
+ var dateFns = require('date-fns');
18
+ var reactDayPicker = require('react-day-picker');
17
19
 
18
20
  function _interopNamespace(e) {
19
21
  if (e && e.__esModule) return e;
@@ -42,6 +44,22 @@ var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive)
42
44
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
43
45
  var ScrollAreaPrimitive__namespace = /*#__PURE__*/_interopNamespace(ScrollAreaPrimitive);
44
46
 
47
+ var CalendarContext = React2.createContext(null);
48
+ function useCalendarContext() {
49
+ return React2.useContext(CalendarContext);
50
+ }
51
+ var ChatSidebarContext = React2.createContext(
52
+ null
53
+ );
54
+ function useChatSidebar() {
55
+ const ctx = React2.useContext(ChatSidebarContext);
56
+ if (!ctx) {
57
+ throw new Error("useChatSidebar must be used within a ChatSidebarProvider");
58
+ }
59
+ return ctx;
60
+ }
61
+
62
+ // src/contexts/index.ts
45
63
  var NotificationsContext = React2.createContext(null);
46
64
  function useNotificationsContext() {
47
65
  return React2.useContext(NotificationsContext);
@@ -5408,10 +5426,1531 @@ function ScrollBar({
5408
5426
  }
5409
5427
  );
5410
5428
  }
5429
+ function UpcomingEvents({
5430
+ events,
5431
+ maxVisible = 5,
5432
+ onEventClick,
5433
+ className
5434
+ }) {
5435
+ const visible = events.slice(0, maxVisible);
5436
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("space-y-1", className), children: visible.map((event) => {
5437
+ const start = new Date(event.dtstart);
5438
+ const end = new Date(event.dtend);
5439
+ const isToday = start.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
5440
+ const dateStr = isToday ? "Today" : start.toLocaleDateString([], {
5441
+ weekday: "short",
5442
+ month: "short",
5443
+ day: "numeric"
5444
+ });
5445
+ const timeStr = event.allDay ? "All day" : `${start.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} \u2013 ${end.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}`;
5446
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5447
+ "button",
5448
+ {
5449
+ type: "button",
5450
+ onClick: () => onEventClick?.(event),
5451
+ className: "flex w-full items-start gap-3 rounded-md px-2 py-2 text-left transition-colors hover:bg-accent",
5452
+ children: [
5453
+ /* @__PURE__ */ jsxRuntime.jsx(
5454
+ "div",
5455
+ {
5456
+ className: cn(
5457
+ "mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md text-xs font-medium",
5458
+ isToday ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground"
5459
+ ),
5460
+ children: start.getDate()
5461
+ }
5462
+ ),
5463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
5464
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: event.summary }),
5465
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
5466
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
5467
+ dateStr,
5468
+ " \xB7 ",
5469
+ timeStr
5470
+ ] }) }),
5471
+ event.location && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-muted-foreground mt-0.5", children: [
5472
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "h-3 w-3" }),
5473
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: event.location })
5474
+ ] })
5475
+ ] })
5476
+ ]
5477
+ },
5478
+ event.id
5479
+ );
5480
+ }) });
5481
+ }
5482
+ function CalendarSubscribeButton({
5483
+ feedUrl,
5484
+ label
5485
+ }) {
5486
+ const [copied, setCopied] = React2.useState(false);
5487
+ const webcalUrl = feedUrl.replace(/^https?:\/\//, "webcal://");
5488
+ const handleCopy = async () => {
5489
+ try {
5490
+ await navigator.clipboard.writeText(webcalUrl);
5491
+ setCopied(true);
5492
+ setTimeout(() => setCopied(false), 2e3);
5493
+ } catch {
5494
+ window.open(webcalUrl, "_blank");
5495
+ }
5496
+ };
5497
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
5498
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5499
+ Button,
5500
+ {
5501
+ variant: "ghost",
5502
+ size: "sm",
5503
+ className: "text-xs",
5504
+ onClick: handleCopy,
5505
+ children: [
5506
+ copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "mr-1 h-3 w-3" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "mr-1 h-3 w-3" }),
5507
+ label ?? (copied ? "Copied!" : "Subscribe")
5508
+ ]
5509
+ }
5510
+ ) }),
5511
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: "Copy webcal URL to subscribe in your calendar app" })
5512
+ ] }) });
5513
+ }
5514
+ function CalendarWidget({
5515
+ calendarUrl,
5516
+ labels = {},
5517
+ onEventClick,
5518
+ className
5519
+ }) {
5520
+ const calCtx = useCalendarContext();
5521
+ const upcomingEvents = (calCtx?.events ?? []).filter((e) => new Date(e.dtstart) >= /* @__PURE__ */ new Date()).sort(
5522
+ (a, b) => new Date(a.dtstart).getTime() - new Date(b.dtstart).getTime()
5523
+ ).slice(0, 5);
5524
+ const hasUpcoming = upcomingEvents.length > 0;
5525
+ return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { children: [
5526
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
5527
+ Button,
5528
+ {
5529
+ variant: "ghost",
5530
+ size: "icon",
5531
+ className: cn("relative h-9 w-9", className),
5532
+ "aria-label": labels.title ?? "Calendar",
5533
+ children: [
5534
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { className: "h-4 w-4" }),
5535
+ hasUpcoming && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-1.5 top-1.5 h-2 w-2 rounded-full bg-primary" })
5536
+ ]
5537
+ }
5538
+ ) }),
5539
+ /* @__PURE__ */ jsxRuntime.jsxs(PopoverContent, { className: "w-80 p-0", align: "end", children: [
5540
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: labels.title ?? "Calendar" }) }),
5541
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "max-h-[320px]", children: calCtx?.isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) }) : upcomingEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-center px-4", children: [
5542
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { className: "mb-2 h-8 w-8 text-muted-foreground/50" }),
5543
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noEvents ?? "No upcoming events" })
5544
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
5545
+ UpcomingEvents,
5546
+ {
5547
+ events: upcomingEvents,
5548
+ onEventClick,
5549
+ className: "p-2"
5550
+ }
5551
+ ) }),
5552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-3 py-2", children: [
5553
+ calendarUrl && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "ghost", size: "sm", className: "text-xs", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: calendarUrl, children: [
5554
+ labels.viewCalendar ?? "View full calendar",
5555
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "ml-1 h-3 w-3" })
5556
+ ] }) }),
5557
+ calCtx?.feedUrl && /* @__PURE__ */ jsxRuntime.jsx(CalendarSubscribeButton, { feedUrl: calCtx.feedUrl })
5558
+ ] })
5559
+ ] })
5560
+ ] });
5561
+ }
5562
+ function CalendarView({
5563
+ events,
5564
+ isLoading,
5565
+ onEventClick,
5566
+ onDateSelect,
5567
+ onCreateEvent,
5568
+ labels = {},
5569
+ className
5570
+ }) {
5571
+ const [viewMode, setViewMode] = React2.useState("month");
5572
+ const [currentDate, setCurrentDate] = React2.useState(/* @__PURE__ */ new Date());
5573
+ const [selectedDate, setSelectedDate] = React2.useState();
5574
+ const navigateBack = () => {
5575
+ if (viewMode === "month") {
5576
+ setCurrentDate((d) => dateFns.subMonths(d, 1));
5577
+ } else if (viewMode === "week") {
5578
+ setCurrentDate((d) => new Date(d.getTime() - 7 * 864e5));
5579
+ } else {
5580
+ setCurrentDate((d) => new Date(d.getTime() - 864e5));
5581
+ }
5582
+ };
5583
+ const navigateForward = () => {
5584
+ if (viewMode === "month") {
5585
+ setCurrentDate((d) => dateFns.addMonths(d, 1));
5586
+ } else if (viewMode === "week") {
5587
+ setCurrentDate((d) => new Date(d.getTime() + 7 * 864e5));
5588
+ } else {
5589
+ setCurrentDate((d) => new Date(d.getTime() + 864e5));
5590
+ }
5591
+ };
5592
+ const goToToday = () => {
5593
+ setCurrentDate(/* @__PURE__ */ new Date());
5594
+ setSelectedDate(/* @__PURE__ */ new Date());
5595
+ };
5596
+ const handleDayClick = (date) => {
5597
+ setSelectedDate(date);
5598
+ onDateSelect?.(date);
5599
+ };
5600
+ const eventsByDate = React2.useMemo(() => {
5601
+ const map = /* @__PURE__ */ new Map();
5602
+ for (const event of events) {
5603
+ const key = new Date(event.dtstart).toDateString();
5604
+ const list = map.get(key) ?? [];
5605
+ list.push(event);
5606
+ map.set(key, list);
5607
+ }
5608
+ return map;
5609
+ }, [events]);
5610
+ const visibleDays = React2.useMemo(() => {
5611
+ if (viewMode === "month") {
5612
+ const monthStart = dateFns.startOfMonth(currentDate);
5613
+ const monthEnd = dateFns.endOfMonth(currentDate);
5614
+ return dateFns.eachDayOfInterval({
5615
+ start: dateFns.startOfWeek(monthStart),
5616
+ end: dateFns.endOfWeek(monthEnd)
5617
+ });
5618
+ } else if (viewMode === "week") {
5619
+ const weekStart = dateFns.startOfWeek(currentDate);
5620
+ const weekEnd = dateFns.endOfWeek(currentDate);
5621
+ return dateFns.eachDayOfInterval({ start: weekStart, end: weekEnd });
5622
+ }
5623
+ return [currentDate];
5624
+ }, [viewMode, currentDate]);
5625
+ const selectedDayEvents = selectedDate ? eventsByDate.get(selectedDate.toDateString()) ?? [] : [];
5626
+ const headerLabel = viewMode === "day" ? dateFns.format(currentDate, "EEEE, MMMM d, yyyy") : dateFns.format(currentDate, "MMMM yyyy");
5627
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex h-full flex-col", className), children: [
5628
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
5629
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
5630
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
5631
+ /* @__PURE__ */ jsxRuntime.jsx(
5632
+ Button,
5633
+ {
5634
+ variant: "ghost",
5635
+ size: "icon",
5636
+ className: "h-8 w-8",
5637
+ onClick: navigateBack,
5638
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
5639
+ }
5640
+ ),
5641
+ /* @__PURE__ */ jsxRuntime.jsx(
5642
+ Button,
5643
+ {
5644
+ variant: "ghost",
5645
+ size: "icon",
5646
+ className: "h-8 w-8",
5647
+ onClick: navigateForward,
5648
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
5649
+ }
5650
+ )
5651
+ ] }),
5652
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: headerLabel }),
5653
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", onClick: goToToday, children: labels.today ?? "Today" })
5654
+ ] }),
5655
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
5656
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex rounded-md border", children: ["month", "week", "day"].map((mode) => /* @__PURE__ */ jsxRuntime.jsx(
5657
+ Button,
5658
+ {
5659
+ variant: viewMode === mode ? "default" : "ghost",
5660
+ size: "sm",
5661
+ className: "rounded-none first:rounded-l-md last:rounded-r-md",
5662
+ onClick: () => setViewMode(mode),
5663
+ children: labels[mode] ?? mode.charAt(0).toUpperCase() + mode.slice(1)
5664
+ },
5665
+ mode
5666
+ )) }),
5667
+ onCreateEvent && /* @__PURE__ */ jsxRuntime.jsxs(Button, { size: "sm", onClick: onCreateEvent, children: [
5668
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "mr-1 h-4 w-4" }),
5669
+ labels.newEvent ?? "New event"
5670
+ ] })
5671
+ ] })
5672
+ ] }),
5673
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-8 w-8 animate-spin text-muted-foreground" }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
5674
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto p-4", children: viewMode === "month" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-7 gap-px rounded-lg border bg-border", children: [
5675
+ ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(
5676
+ (day) => /* @__PURE__ */ jsxRuntime.jsx(
5677
+ "div",
5678
+ {
5679
+ className: "bg-muted px-2 py-1.5 text-center text-xs font-medium text-muted-foreground",
5680
+ children: day
5681
+ },
5682
+ day
5683
+ )
5684
+ ),
5685
+ visibleDays.map((day) => {
5686
+ const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
5687
+ const isCurrentMonth = day.getMonth() === currentDate.getMonth();
5688
+ const isToday = dateFns.isSameDay(day, /* @__PURE__ */ new Date());
5689
+ const isSelected = selectedDate && dateFns.isSameDay(day, selectedDate);
5690
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5691
+ "button",
5692
+ {
5693
+ type: "button",
5694
+ onClick: () => handleDayClick(day),
5695
+ className: cn(
5696
+ "min-h-[80px] bg-background p-1 text-left transition-colors hover:bg-accent",
5697
+ !isCurrentMonth && "opacity-40",
5698
+ isSelected && "ring-2 ring-primary ring-inset"
5699
+ ),
5700
+ children: [
5701
+ /* @__PURE__ */ jsxRuntime.jsx(
5702
+ "span",
5703
+ {
5704
+ className: cn(
5705
+ "inline-flex h-6 w-6 items-center justify-center rounded-full text-xs",
5706
+ isToday && "bg-primary text-primary-foreground font-bold"
5707
+ ),
5708
+ children: day.getDate()
5709
+ }
5710
+ ),
5711
+ dayEvents.slice(0, 3).map((evt) => /* @__PURE__ */ jsxRuntime.jsx(
5712
+ "div",
5713
+ {
5714
+ className: "mt-0.5 truncate rounded bg-primary/10 px-1 py-0.5 text-[10px] text-primary",
5715
+ onClick: (e) => {
5716
+ e.stopPropagation();
5717
+ onEventClick?.(evt);
5718
+ },
5719
+ role: "button",
5720
+ tabIndex: 0,
5721
+ onKeyDown: (e) => {
5722
+ if (e.key === "Enter") onEventClick?.(evt);
5723
+ },
5724
+ children: evt.summary
5725
+ },
5726
+ evt.id
5727
+ )),
5728
+ dayEvents.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-0.5 text-[10px] text-muted-foreground", children: [
5729
+ "+",
5730
+ dayEvents.length - 3,
5731
+ " more"
5732
+ ] })
5733
+ ]
5734
+ },
5735
+ day.toISOString()
5736
+ );
5737
+ })
5738
+ ] }) : viewMode === "week" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-2", children: visibleDays.map((day) => {
5739
+ const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
5740
+ const isToday = dateFns.isSameDay(day, /* @__PURE__ */ new Date());
5741
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-h-[200px]", children: [
5742
+ /* @__PURE__ */ jsxRuntime.jsxs(
5743
+ "div",
5744
+ {
5745
+ className: cn(
5746
+ "mb-2 text-center text-xs",
5747
+ isToday && "font-bold text-primary"
5748
+ ),
5749
+ children: [
5750
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: dateFns.format(day, "EEE") }),
5751
+ /* @__PURE__ */ jsxRuntime.jsx(
5752
+ "p",
5753
+ {
5754
+ className: cn(
5755
+ "inline-flex h-7 w-7 items-center justify-center rounded-full",
5756
+ isToday && "bg-primary text-primary-foreground"
5757
+ ),
5758
+ children: day.getDate()
5759
+ }
5760
+ )
5761
+ ]
5762
+ }
5763
+ ),
5764
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: dayEvents.map((evt) => /* @__PURE__ */ jsxRuntime.jsxs(
5765
+ "button",
5766
+ {
5767
+ type: "button",
5768
+ onClick: () => onEventClick?.(evt),
5769
+ className: "w-full rounded bg-primary/10 px-2 py-1 text-left text-xs text-primary transition-colors hover:bg-primary/20",
5770
+ children: [
5771
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: evt.summary }),
5772
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] opacity-70", children: dateFns.format(new Date(evt.dtstart), "h:mm a") })
5773
+ ]
5774
+ },
5775
+ evt.id
5776
+ )) })
5777
+ ] }, day.toISOString());
5778
+ }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: visibleDays.flatMap((day) => {
5779
+ const dayEvents = eventsByDate.get(day.toDateString()) ?? [];
5780
+ if (dayEvents.length === 0) {
5781
+ return [
5782
+ /* @__PURE__ */ jsxRuntime.jsxs(
5783
+ "div",
5784
+ {
5785
+ className: "flex flex-col items-center justify-center py-16 text-center",
5786
+ children: [
5787
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { className: "mb-3 h-10 w-10 text-muted-foreground/50" }),
5788
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noEvents ?? "No events for this day" })
5789
+ ]
5790
+ },
5791
+ "empty"
5792
+ )
5793
+ ];
5794
+ }
5795
+ return dayEvents.map((evt) => /* @__PURE__ */ jsxRuntime.jsxs(
5796
+ "button",
5797
+ {
5798
+ type: "button",
5799
+ onClick: () => onEventClick?.(evt),
5800
+ className: "flex w-full items-center gap-3 rounded-lg border p-3 text-left transition-colors hover:bg-accent",
5801
+ children: [
5802
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-primary flex-shrink-0" }),
5803
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
5804
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: evt.summary }),
5805
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-muted-foreground", children: [
5806
+ dateFns.format(new Date(evt.dtstart), "h:mm a"),
5807
+ " \u2013",
5808
+ " ",
5809
+ dateFns.format(new Date(evt.dtend), "h:mm a")
5810
+ ] }),
5811
+ evt.location && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: evt.location })
5812
+ ] })
5813
+ ]
5814
+ },
5815
+ evt.id
5816
+ ));
5817
+ }) }) }),
5818
+ selectedDate && selectedDayEvents.length > 0 && viewMode === "month" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-64 border-l p-3", children: [
5819
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "mb-2 text-sm font-semibold", children: dateFns.format(selectedDate, "MMMM d") }),
5820
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: selectedDayEvents.map((evt) => /* @__PURE__ */ jsxRuntime.jsxs(
5821
+ "button",
5822
+ {
5823
+ type: "button",
5824
+ onClick: () => onEventClick?.(evt),
5825
+ className: "w-full rounded-md bg-muted p-2 text-left text-sm transition-colors hover:bg-accent",
5826
+ children: [
5827
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium truncate", children: evt.summary }),
5828
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: evt.allDay ? "All day" : `${dateFns.format(new Date(evt.dtstart), "h:mm a")} \u2013 ${dateFns.format(new Date(evt.dtend), "h:mm a")}` })
5829
+ ]
5830
+ },
5831
+ evt.id
5832
+ )) })
5833
+ ] })
5834
+ ] })
5835
+ ] });
5836
+ }
5837
+ function EventDialog({
5838
+ open,
5839
+ onOpenChange,
5840
+ event,
5841
+ onSave,
5842
+ onDelete,
5843
+ labels = {}
5844
+ }) {
5845
+ const isEdit = !!event?.id;
5846
+ const [summary, setSummary] = React2.useState(event?.summary ?? "");
5847
+ const [description, setDescription] = React2.useState(event?.description ?? "");
5848
+ const [location, setLocation] = React2.useState(event?.location ?? "");
5849
+ const [dtstart, setDtstart] = React2.useState(
5850
+ event?.dtstart ? new Date(event.dtstart).toISOString().slice(0, 16) : ""
5851
+ );
5852
+ const [dtend, setDtend] = React2.useState(
5853
+ event?.dtend ? new Date(event.dtend).toISOString().slice(0, 16) : ""
5854
+ );
5855
+ const [allDay, setAllDay] = React2.useState(event?.allDay ?? false);
5856
+ const [saving, setSaving] = React2.useState(false);
5857
+ const handleSave = async () => {
5858
+ if (!summary.trim() || !dtstart || !dtend) return;
5859
+ setSaving(true);
5860
+ try {
5861
+ await onSave({
5862
+ ...event?.id ? { id: event.id } : {},
5863
+ summary: summary.trim(),
5864
+ description: description.trim() || null,
5865
+ location: location.trim() || null,
5866
+ dtstart: new Date(dtstart).toISOString(),
5867
+ dtend: new Date(dtend).toISOString(),
5868
+ allDay
5869
+ });
5870
+ onOpenChange(false);
5871
+ } finally {
5872
+ setSaving(false);
5873
+ }
5874
+ };
5875
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-[480px]", children: [
5876
+ /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: isEdit ? labels.editTitle ?? "Edit event" : labels.createTitle ?? "New event" }) }),
5877
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4 py-2", children: [
5878
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5879
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.summary ?? "Summary" }),
5880
+ /* @__PURE__ */ jsxRuntime.jsx(
5881
+ "input",
5882
+ {
5883
+ value: summary,
5884
+ onChange: (e) => setSummary(e.target.value),
5885
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
5886
+ placeholder: "Event name"
5887
+ }
5888
+ )
5889
+ ] }),
5890
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
5891
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5892
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mb-1 block text-sm font-medium", children: [
5893
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "mr-1 inline h-3.5 w-3.5" }),
5894
+ labels.startDate ?? "Start"
5895
+ ] }),
5896
+ /* @__PURE__ */ jsxRuntime.jsx(
5897
+ "input",
5898
+ {
5899
+ type: "datetime-local",
5900
+ value: dtstart,
5901
+ onChange: (e) => setDtstart(e.target.value),
5902
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
5903
+ }
5904
+ )
5905
+ ] }),
5906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5907
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.endDate ?? "End" }),
5908
+ /* @__PURE__ */ jsxRuntime.jsx(
5909
+ "input",
5910
+ {
5911
+ type: "datetime-local",
5912
+ value: dtend,
5913
+ onChange: (e) => setDtend(e.target.value),
5914
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
5915
+ }
5916
+ )
5917
+ ] })
5918
+ ] }),
5919
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
5920
+ /* @__PURE__ */ jsxRuntime.jsx(
5921
+ "input",
5922
+ {
5923
+ type: "checkbox",
5924
+ checked: allDay,
5925
+ onChange: (e) => setAllDay(e.target.checked),
5926
+ className: "rounded border-input"
5927
+ }
5928
+ ),
5929
+ labels.allDay ?? "All day"
5930
+ ] }),
5931
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5932
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mb-1 block text-sm font-medium", children: [
5933
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MapPin, { className: "mr-1 inline h-3.5 w-3.5" }),
5934
+ labels.location ?? "Location"
5935
+ ] }),
5936
+ /* @__PURE__ */ jsxRuntime.jsx(
5937
+ "input",
5938
+ {
5939
+ value: location,
5940
+ onChange: (e) => setLocation(e.target.value),
5941
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
5942
+ placeholder: "Add location"
5943
+ }
5944
+ )
5945
+ ] }),
5946
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
5947
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-sm font-medium", children: labels.description ?? "Description" }),
5948
+ /* @__PURE__ */ jsxRuntime.jsx(
5949
+ "textarea",
5950
+ {
5951
+ value: description,
5952
+ onChange: (e) => setDescription(e.target.value),
5953
+ rows: 3,
5954
+ className: "w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring resize-none",
5955
+ placeholder: "Add description"
5956
+ }
5957
+ )
5958
+ ] })
5959
+ ] }),
5960
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { className: "gap-2", children: [
5961
+ isEdit && onDelete && /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "destructive", size: "sm", onClick: onDelete, children: labels.delete ?? "Delete" }),
5962
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
5963
+ /* @__PURE__ */ jsxRuntime.jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", size: "sm", children: labels.cancel ?? "Cancel" }) }),
5964
+ /* @__PURE__ */ jsxRuntime.jsxs(
5965
+ Button,
5966
+ {
5967
+ size: "sm",
5968
+ onClick: handleSave,
5969
+ disabled: saving || !summary.trim() || !dtstart || !dtend,
5970
+ children: [
5971
+ saving && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-1 h-3.5 w-3.5 animate-spin" }),
5972
+ labels.save ?? "Save"
5973
+ ]
5974
+ }
5975
+ )
5976
+ ] })
5977
+ ] }) });
5978
+ }
5979
+ function MiniCalendar({
5980
+ selected,
5981
+ onSelect,
5982
+ events = [],
5983
+ className
5984
+ }) {
5985
+ const eventDates = new Set(
5986
+ events.map((e) => new Date(e.dtstart).toDateString())
5987
+ );
5988
+ return /* @__PURE__ */ jsxRuntime.jsx(
5989
+ reactDayPicker.DayPicker,
5990
+ {
5991
+ mode: "single",
5992
+ selected,
5993
+ onSelect,
5994
+ showOutsideDays: true,
5995
+ className: cn("p-2", className),
5996
+ classNames: {
5997
+ months: "flex flex-col",
5998
+ month: "space-y-2",
5999
+ month_caption: "flex justify-center relative items-center",
6000
+ caption_label: "text-sm font-medium",
6001
+ nav: "space-x-1 flex items-center",
6002
+ button_previous: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1",
6003
+ button_next: "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1",
6004
+ month_grid: "w-full border-collapse space-y-1",
6005
+ weekdays: "flex",
6006
+ weekday: "text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
6007
+ week: "flex w-full mt-1",
6008
+ day: "relative p-0 text-center text-sm focus-within:relative focus-within:z-20",
6009
+ day_button: cn(
6010
+ "h-8 w-8 p-0 font-normal",
6011
+ "hover:bg-accent hover:text-accent-foreground rounded-md"
6012
+ ),
6013
+ selected: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground rounded-md",
6014
+ today: "bg-accent text-accent-foreground rounded-md",
6015
+ outside: "text-muted-foreground opacity-50",
6016
+ disabled: "text-muted-foreground opacity-50"
6017
+ },
6018
+ modifiers: {
6019
+ hasEvent: (date) => eventDates.has(date.toDateString())
6020
+ },
6021
+ modifiersClassNames: {
6022
+ hasEvent: "font-bold"
6023
+ }
6024
+ }
6025
+ );
6026
+ }
6027
+ var statusConfig = {
6028
+ ACCEPTED: {
6029
+ icon: lucideReact.Check,
6030
+ label: "Accepted",
6031
+ className: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/30"
6032
+ },
6033
+ DECLINED: {
6034
+ icon: lucideReact.X,
6035
+ label: "Declined",
6036
+ className: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/30"
6037
+ },
6038
+ TENTATIVE: {
6039
+ icon: lucideReact.HelpCircle,
6040
+ label: "Tentative",
6041
+ className: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 border-yellow-500/30"
6042
+ },
6043
+ "NEEDS-ACTION": {
6044
+ icon: lucideReact.HelpCircle,
6045
+ label: "Pending",
6046
+ className: "bg-muted text-muted-foreground border-border"
6047
+ }
6048
+ };
6049
+ function EventRsvpBadge({
6050
+ eventId,
6051
+ currentStatus,
6052
+ onRsvp,
6053
+ className
6054
+ }) {
6055
+ const [loading, setLoading] = React2.useState(null);
6056
+ const status = currentStatus?.toUpperCase() || "NEEDS-ACTION";
6057
+ const handleRsvp = async (partstat) => {
6058
+ if (partstat === status) return;
6059
+ setLoading(partstat);
6060
+ try {
6061
+ await onRsvp(eventId, partstat);
6062
+ } finally {
6063
+ setLoading(null);
6064
+ }
6065
+ };
6066
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex items-center gap-1", className), children: ["ACCEPTED", "TENTATIVE", "DECLINED"].map((ps) => {
6067
+ const cfg = statusConfig[ps];
6068
+ const Icon = cfg.icon;
6069
+ const isActive = status === ps;
6070
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6071
+ Button,
6072
+ {
6073
+ variant: "outline",
6074
+ size: "sm",
6075
+ className: cn("h-7 px-2 text-xs", isActive && cfg.className),
6076
+ onClick: () => handleRsvp(ps),
6077
+ disabled: loading !== null,
6078
+ children: [
6079
+ loading === ps ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "mr-1 h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "mr-1 h-3 w-3" }),
6080
+ cfg.label
6081
+ ]
6082
+ },
6083
+ ps
6084
+ );
6085
+ }) });
6086
+ }
6087
+ var STORAGE_KEY = "cartera:chatSidebarOpen";
6088
+ function ChatSidebarProvider({
6089
+ fetchConversations,
6090
+ children,
6091
+ defaultOpen
6092
+ }) {
6093
+ const [isOpen, setIsOpen] = React2.useState(() => {
6094
+ if (defaultOpen !== void 0) return defaultOpen;
6095
+ if (typeof window === "undefined") return false;
6096
+ try {
6097
+ return localStorage.getItem(STORAGE_KEY) === "true";
6098
+ } catch {
6099
+ return false;
6100
+ }
6101
+ });
6102
+ const [view, setView] = React2.useState("list");
6103
+ const [conversations, setConversations] = React2.useState([]);
6104
+ const [isLoading, setIsLoading] = React2.useState(false);
6105
+ const [activeCaseId, setActiveCaseId] = React2.useState(null);
6106
+ const [activeRoomId, setActiveRoomId] = React2.useState(null);
6107
+ const fetchRef = React2.useRef(fetchConversations);
6108
+ fetchRef.current = fetchConversations;
6109
+ const persistOpen = React2.useCallback((open2) => {
6110
+ setIsOpen(open2);
6111
+ try {
6112
+ localStorage.setItem(STORAGE_KEY, String(open2));
6113
+ } catch {
6114
+ }
6115
+ }, []);
6116
+ const loadConversations = React2.useCallback(async () => {
6117
+ setIsLoading(true);
6118
+ try {
6119
+ const result = await fetchRef.current();
6120
+ setConversations(result);
6121
+ } catch {
6122
+ } finally {
6123
+ setIsLoading(false);
6124
+ }
6125
+ }, []);
6126
+ React2.useEffect(() => {
6127
+ void loadConversations();
6128
+ }, [loadConversations]);
6129
+ const toggle = React2.useCallback(() => persistOpen(!isOpen), [isOpen, persistOpen]);
6130
+ const open = React2.useCallback(() => persistOpen(true), [persistOpen]);
6131
+ const close = React2.useCallback(() => {
6132
+ persistOpen(false);
6133
+ setView("list");
6134
+ setActiveCaseId(null);
6135
+ setActiveRoomId(null);
6136
+ }, [persistOpen]);
6137
+ const openChat = React2.useCallback(
6138
+ (caseId, roomId) => {
6139
+ persistOpen(true);
6140
+ setActiveCaseId(caseId);
6141
+ if (roomId) {
6142
+ setActiveRoomId(roomId);
6143
+ setView("chat");
6144
+ } else {
6145
+ const conv = conversations.find((c) => c.caseId === caseId);
6146
+ if (conv && conv.rooms.length === 1) {
6147
+ setActiveRoomId(conv.rooms[0].id);
6148
+ setView("chat");
6149
+ } else if (conv && conv.rooms.length > 1) {
6150
+ setView("list");
6151
+ }
6152
+ }
6153
+ },
6154
+ [persistOpen, conversations]
6155
+ );
6156
+ const selectRoom = React2.useCallback(
6157
+ (roomId, caseId) => {
6158
+ setActiveCaseId(caseId);
6159
+ setActiveRoomId(roomId);
6160
+ setView("chat");
6161
+ if (!isOpen) persistOpen(true);
6162
+ },
6163
+ [isOpen, persistOpen]
6164
+ );
6165
+ const back = React2.useCallback(() => {
6166
+ setView("list");
6167
+ setActiveRoomId(null);
6168
+ }, []);
6169
+ const value = {
6170
+ isOpen,
6171
+ view,
6172
+ conversations,
6173
+ isLoading,
6174
+ activeCaseId,
6175
+ activeRoomId,
6176
+ toggle,
6177
+ open,
6178
+ close,
6179
+ openChat,
6180
+ selectRoom,
6181
+ back,
6182
+ refetch: loadConversations
6183
+ };
6184
+ return /* @__PURE__ */ jsxRuntime.jsx(ChatSidebarContext.Provider, { value, children });
6185
+ }
6186
+ var PING_INTERVAL = 3e4;
6187
+ var MIN_RECONNECT_DELAY = 1e3;
6188
+ var MAX_RECONNECT_DELAY = 3e4;
6189
+ function useChatRoom(roomId, config, options = {}) {
6190
+ const {
6191
+ minReconnectDelay = MIN_RECONNECT_DELAY,
6192
+ maxReconnectDelay = MAX_RECONNECT_DELAY,
6193
+ pingInterval = PING_INTERVAL
6194
+ } = options;
6195
+ const [messages, setMessages] = React2.useState([]);
6196
+ const [isConnected, setIsConnected] = React2.useState(false);
6197
+ const [error, setError] = React2.useState(null);
6198
+ const [typingUsers, setTypingUsers] = React2.useState([]);
6199
+ const [hasMoreHistory, setHasMoreHistory] = React2.useState(true);
6200
+ const [isLoadingHistory, setIsLoadingHistory] = React2.useState(false);
6201
+ const wsRef = React2.useRef(null);
6202
+ const mountedRef = React2.useRef(true);
6203
+ const reconnectDelayRef = React2.useRef(minReconnectDelay);
6204
+ const reconnectTimerRef = React2.useRef(null);
6205
+ const pingTimerRef = React2.useRef(null);
6206
+ const roomIdRef = React2.useRef(roomId);
6207
+ const cursorRef = React2.useRef(null);
6208
+ const messageIdsRef = React2.useRef(/* @__PURE__ */ new Set());
6209
+ const loadMoreHistoryRef = React2.useRef(
6210
+ () => Promise.resolve()
6211
+ );
6212
+ const configRef = React2.useRef(config);
6213
+ roomIdRef.current = roomId;
6214
+ configRef.current = config;
6215
+ const clearTimers = React2.useCallback(() => {
6216
+ if (reconnectTimerRef.current) {
6217
+ clearTimeout(reconnectTimerRef.current);
6218
+ reconnectTimerRef.current = null;
6219
+ }
6220
+ if (pingTimerRef.current) {
6221
+ clearInterval(pingTimerRef.current);
6222
+ pingTimerRef.current = null;
6223
+ }
6224
+ }, []);
6225
+ const loadMoreHistory = React2.useCallback(async () => {
6226
+ if (!roomIdRef.current || isLoadingHistory || !hasMoreHistory) return;
6227
+ setIsLoadingHistory(true);
6228
+ try {
6229
+ const jwt = await configRef.current.getToken();
6230
+ if (!jwt) return;
6231
+ const params = new URLSearchParams({ limit: "50" });
6232
+ if (cursorRef.current) {
6233
+ params.set("cursor", cursorRef.current);
6234
+ }
6235
+ const res = await fetch(
6236
+ `${configRef.current.baseUrl}/rooms/${roomIdRef.current}/messages?${params}`,
6237
+ { headers: { Authorization: `Bearer ${jwt}` } }
6238
+ );
6239
+ if (!res.ok) return;
6240
+ const data = await res.json();
6241
+ if (data.success && data.result.length > 0) {
6242
+ const newMessages = data.result.filter(
6243
+ (m) => !messageIdsRef.current.has(m.id)
6244
+ );
6245
+ for (const m of newMessages) {
6246
+ messageIdsRef.current.add(m.id);
6247
+ }
6248
+ setMessages((prev) => [...newMessages, ...prev]);
6249
+ cursorRef.current = data.nextCursor;
6250
+ setHasMoreHistory(data.nextCursor !== null);
6251
+ } else {
6252
+ setHasMoreHistory(false);
6253
+ }
6254
+ } catch {
6255
+ } finally {
6256
+ setIsLoadingHistory(false);
6257
+ }
6258
+ }, [isLoadingHistory, hasMoreHistory]);
6259
+ const sendMessage = React2.useCallback(
6260
+ (content, attachmentIds) => {
6261
+ const ws = wsRef.current;
6262
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
6263
+ ws.send(
6264
+ JSON.stringify({
6265
+ type: "send_message",
6266
+ content,
6267
+ attachmentIds,
6268
+ clientMessageId: crypto.randomUUID()
6269
+ })
6270
+ );
6271
+ },
6272
+ []
6273
+ );
6274
+ const sendTyping = React2.useCallback((isTyping) => {
6275
+ const ws = wsRef.current;
6276
+ if (!ws || ws.readyState !== WebSocket.OPEN) return;
6277
+ ws.send(JSON.stringify({ type: "typing", isTyping }));
6278
+ }, []);
6279
+ React2.useEffect(() => {
6280
+ loadMoreHistoryRef.current = loadMoreHistory;
6281
+ }, [loadMoreHistory]);
6282
+ const connect = React2.useCallback(async () => {
6283
+ if (!roomIdRef.current || !mountedRef.current) return;
6284
+ let jwt = null;
6285
+ try {
6286
+ jwt = await configRef.current.getToken();
6287
+ } catch {
6288
+ if (mountedRef.current) {
6289
+ setError("Failed to get authentication token");
6290
+ }
6291
+ return;
6292
+ }
6293
+ if (!jwt || !mountedRef.current) return;
6294
+ const baseUrl = configRef.current.baseUrl;
6295
+ const wsBase = baseUrl.replace(/^http/, "ws");
6296
+ const wsUrl = `${wsBase}/realtime/chat?roomId=${encodeURIComponent(roomIdRef.current)}&token=${encodeURIComponent(jwt)}`;
6297
+ let ws;
6298
+ try {
6299
+ ws = new WebSocket(wsUrl);
6300
+ } catch (err) {
6301
+ if (mountedRef.current) {
6302
+ setError(
6303
+ err instanceof Error ? err.message : "WebSocket creation failed"
6304
+ );
6305
+ }
6306
+ return;
6307
+ }
6308
+ wsRef.current = ws;
6309
+ ws.onopen = () => {
6310
+ if (!mountedRef.current) {
6311
+ ws.close();
6312
+ return;
6313
+ }
6314
+ setIsConnected(true);
6315
+ setError(null);
6316
+ reconnectDelayRef.current = minReconnectDelay;
6317
+ ws.send(JSON.stringify({ type: "client_hello" }));
6318
+ pingTimerRef.current = setInterval(() => {
6319
+ if (ws.readyState === WebSocket.OPEN) {
6320
+ ws.send(JSON.stringify({ type: "ping", timestamp: Date.now() }));
6321
+ }
6322
+ }, pingInterval);
6323
+ };
6324
+ ws.onmessage = (event) => {
6325
+ if (!mountedRef.current) return;
6326
+ try {
6327
+ const msg = JSON.parse(event.data);
6328
+ switch (msg.type) {
6329
+ case "server_hello":
6330
+ void loadMoreHistoryRef.current();
6331
+ break;
6332
+ case "new_message": {
6333
+ const message = msg.message;
6334
+ if (!messageIdsRef.current.has(message.id)) {
6335
+ messageIdsRef.current.add(message.id);
6336
+ setMessages((prev) => [...prev, message]);
6337
+ }
6338
+ setTypingUsers(
6339
+ (prev) => prev.filter((u) => u.userId !== message.senderId)
6340
+ );
6341
+ break;
6342
+ }
6343
+ case "typing_indicator": {
6344
+ const userId = msg.userId;
6345
+ const userName = msg.userName ?? null;
6346
+ const isTyping = msg.isTyping;
6347
+ setTypingUsers((prev) => {
6348
+ if (isTyping) {
6349
+ if (prev.some((u) => u.userId === userId)) return prev;
6350
+ return [...prev, { userId, userName }];
6351
+ }
6352
+ return prev.filter((u) => u.userId !== userId);
6353
+ });
6354
+ break;
6355
+ }
6356
+ case "pong":
6357
+ break;
6358
+ case "error":
6359
+ setError(msg.message ?? "WebSocket error from server");
6360
+ break;
6361
+ }
6362
+ } catch {
6363
+ }
6364
+ };
6365
+ ws.onerror = () => {
6366
+ if (!mountedRef.current) return;
6367
+ setError("WebSocket connection error");
6368
+ };
6369
+ ws.onclose = () => {
6370
+ if (pingTimerRef.current) {
6371
+ clearInterval(pingTimerRef.current);
6372
+ pingTimerRef.current = null;
6373
+ }
6374
+ if (!mountedRef.current) return;
6375
+ setIsConnected(false);
6376
+ const delay = reconnectDelayRef.current;
6377
+ reconnectDelayRef.current = Math.min(delay * 2, maxReconnectDelay);
6378
+ reconnectTimerRef.current = setTimeout(() => {
6379
+ if (mountedRef.current && roomIdRef.current) {
6380
+ void connect();
6381
+ }
6382
+ }, delay);
6383
+ };
6384
+ }, [minReconnectDelay, maxReconnectDelay, pingInterval]);
6385
+ React2.useEffect(() => {
6386
+ mountedRef.current = true;
6387
+ if (!roomId) {
6388
+ setMessages([]);
6389
+ setIsConnected(false);
6390
+ setError(null);
6391
+ setTypingUsers([]);
6392
+ setHasMoreHistory(true);
6393
+ cursorRef.current = null;
6394
+ messageIdsRef.current = /* @__PURE__ */ new Set();
6395
+ return;
6396
+ }
6397
+ setMessages([]);
6398
+ setTypingUsers([]);
6399
+ setHasMoreHistory(true);
6400
+ cursorRef.current = null;
6401
+ messageIdsRef.current = /* @__PURE__ */ new Set();
6402
+ reconnectDelayRef.current = minReconnectDelay;
6403
+ void connect();
6404
+ return () => {
6405
+ mountedRef.current = false;
6406
+ clearTimers();
6407
+ if (wsRef.current) {
6408
+ wsRef.current.close();
6409
+ wsRef.current = null;
6410
+ }
6411
+ };
6412
+ }, [roomId]);
6413
+ return {
6414
+ messages,
6415
+ sendMessage,
6416
+ sendTyping,
6417
+ isConnected,
6418
+ error,
6419
+ typingUsers,
6420
+ loadMoreHistory,
6421
+ hasMoreHistory,
6422
+ isLoadingHistory
6423
+ };
6424
+ }
6425
+ var roleColors = {
6426
+ client: "bg-blue-500/10 text-blue-600 dark:text-blue-400",
6427
+ seller: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400",
6428
+ org_manager: "bg-violet-500/10 text-violet-600 dark:text-violet-400",
6429
+ credit_agent: "bg-amber-500/10 text-amber-600 dark:text-amber-400",
6430
+ admin: "bg-primary/10 text-primary"
6431
+ };
6432
+ function ChatRoomView({
6433
+ roomId,
6434
+ currentUserId,
6435
+ config,
6436
+ roomLabel,
6437
+ labels = {},
6438
+ roleLabel,
6439
+ className
6440
+ }) {
6441
+ const {
6442
+ messages,
6443
+ sendMessage,
6444
+ sendTyping,
6445
+ isConnected,
6446
+ error,
6447
+ typingUsers,
6448
+ loadMoreHistory,
6449
+ hasMoreHistory,
6450
+ isLoadingHistory
6451
+ } = useChatRoom(roomId, config);
6452
+ const [newMessage, setNewMessage] = React2.useState("");
6453
+ const [pendingAttachments, setPendingAttachments] = React2.useState([]);
6454
+ const scrollRef = React2.useRef(null);
6455
+ const inputRef = React2.useRef(null);
6456
+ const fileInputRef = React2.useRef(null);
6457
+ const typingTimeoutRef = React2.useRef(null);
6458
+ const prevMessageCountRef = React2.useRef(0);
6459
+ React2.useEffect(() => {
6460
+ const el = scrollRef.current;
6461
+ if (!el) return;
6462
+ if (messages.length > prevMessageCountRef.current) {
6463
+ el.scrollTop = el.scrollHeight;
6464
+ }
6465
+ prevMessageCountRef.current = messages.length;
6466
+ }, [messages.length]);
6467
+ const handleScroll = React2.useCallback(() => {
6468
+ const el = scrollRef.current;
6469
+ if (!el) return;
6470
+ if (el.scrollTop < 50 && hasMoreHistory && !isLoadingHistory) {
6471
+ void loadMoreHistory();
6472
+ }
6473
+ }, [hasMoreHistory, isLoadingHistory, loadMoreHistory]);
6474
+ const handleTyping = React2.useCallback(() => {
6475
+ sendTyping(true);
6476
+ if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
6477
+ typingTimeoutRef.current = setTimeout(() => sendTyping(false), 2e3);
6478
+ }, [sendTyping]);
6479
+ const handleSendMessage = React2.useCallback(() => {
6480
+ const content = newMessage.trim();
6481
+ const attachmentIds = pendingAttachments.filter((a) => !a.uploading).map((a) => a.id);
6482
+ if (!content && attachmentIds.length === 0) return;
6483
+ sendMessage(content, attachmentIds.length > 0 ? attachmentIds : void 0);
6484
+ setNewMessage("");
6485
+ setPendingAttachments([]);
6486
+ sendTyping(false);
6487
+ if (typingTimeoutRef.current) clearTimeout(typingTimeoutRef.current);
6488
+ inputRef.current?.focus();
6489
+ }, [newMessage, pendingAttachments, sendMessage, sendTyping]);
6490
+ const handleKeyDown = (e) => {
6491
+ if (e.key === "Enter" && !e.shiftKey) {
6492
+ e.preventDefault();
6493
+ handleSendMessage();
6494
+ }
6495
+ };
6496
+ const handleFileSelect = async (e) => {
6497
+ const files = e.target.files;
6498
+ if (!files || files.length === 0) return;
6499
+ for (const file of Array.from(files)) {
6500
+ const tempId = crypto.randomUUID();
6501
+ const pending = {
6502
+ id: tempId,
6503
+ fileName: file.name,
6504
+ fileSize: file.size,
6505
+ mimeType: file.type || "application/octet-stream",
6506
+ uploading: true
6507
+ };
6508
+ setPendingAttachments((prev) => [...prev, pending]);
6509
+ try {
6510
+ const jwt = await config.getToken();
6511
+ if (!jwt) throw new Error("No auth token");
6512
+ const formData = new FormData();
6513
+ formData.append("file", file);
6514
+ const res = await fetch(
6515
+ `${config.baseUrl}/rooms/${roomId}/attachments/upload`,
6516
+ {
6517
+ method: "POST",
6518
+ headers: { Authorization: `Bearer ${jwt}` },
6519
+ body: formData
6520
+ }
6521
+ );
6522
+ if (!res.ok) throw new Error("Upload failed");
6523
+ const data = await res.json();
6524
+ setPendingAttachments(
6525
+ (prev) => prev.map(
6526
+ (a) => a.id === tempId ? { ...a, id: data.result.id, uploading: false } : a
6527
+ )
6528
+ );
6529
+ } catch {
6530
+ setPendingAttachments((prev) => prev.filter((a) => a.id !== tempId));
6531
+ }
6532
+ }
6533
+ if (fileInputRef.current) fileInputRef.current.value = "";
6534
+ };
6535
+ const removeAttachment = (id) => {
6536
+ setPendingAttachments((prev) => prev.filter((a) => a.id !== id));
6537
+ };
6538
+ const formatTime = (timestamp) => {
6539
+ return new Date(timestamp).toLocaleTimeString([], {
6540
+ hour: "2-digit",
6541
+ minute: "2-digit"
6542
+ });
6543
+ };
6544
+ const formatDate = (timestamp) => {
6545
+ const date = new Date(timestamp);
6546
+ const today = /* @__PURE__ */ new Date();
6547
+ const yesterday = new Date(today);
6548
+ yesterday.setDate(yesterday.getDate() - 1);
6549
+ if (date.toDateString() === today.toDateString())
6550
+ return labels.today ?? "Today";
6551
+ if (date.toDateString() === yesterday.toDateString())
6552
+ return labels.yesterday ?? "Yesterday";
6553
+ return date.toLocaleDateString([], { month: "short", day: "numeric" });
6554
+ };
6555
+ const groupedMessages = messages.reduce(
6556
+ (groups, message) => {
6557
+ const date = formatDate(message.createdAt);
6558
+ if (!groups[date]) groups[date] = [];
6559
+ groups[date].push(message);
6560
+ return groups;
6561
+ },
6562
+ {}
6563
+ );
6564
+ const formatFileSize = (bytes) => {
6565
+ if (bytes < 1024) return `${bytes} B`;
6566
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
6567
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
6568
+ };
6569
+ const getAttachmentUrl = (attachmentId) => `${config.baseUrl}/rooms/${roomId}/attachments/${attachmentId}`;
6570
+ const getRoleLabel = (role) => roleLabel ? roleLabel(role) : role;
6571
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex h-full flex-col", className), children: [
6572
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-shrink-0 flex items-center justify-between border-b px-4 py-3", children: [
6573
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6574
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-4 w-4 text-primary", "aria-hidden": "true" }),
6575
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: roomLabel ?? "Chat" })
6576
+ ] }),
6577
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6578
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: isConnected ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Wifi, { className: "h-3.5 w-3.5 text-green-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.WifiOff, { className: "h-3.5 w-3.5 text-destructive" }) }) }),
6579
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: isConnected ? "Connected" : error ?? "Reconnecting..." })
6580
+ ] }) })
6581
+ ] }),
6582
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
6583
+ /* @__PURE__ */ jsxRuntime.jsxs(
6584
+ "div",
6585
+ {
6586
+ ref: scrollRef,
6587
+ onScroll: handleScroll,
6588
+ className: "flex-1 overflow-y-auto p-3",
6589
+ children: [
6590
+ isLoadingHistory && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center py-2", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }) }),
6591
+ messages.length === 0 && !isLoadingHistory ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full flex-col items-center justify-center py-8 text-center", children: [
6592
+ /* @__PURE__ */ jsxRuntime.jsx(
6593
+ lucideReact.MessageSquare,
6594
+ {
6595
+ className: "mb-3 h-10 w-10 text-muted-foreground/50",
6596
+ "aria-hidden": "true"
6597
+ }
6598
+ ),
6599
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noMessages ?? "No messages yet" })
6600
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: Object.entries(groupedMessages).map(([date, dateMessages]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
6601
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "my-3 flex items-center gap-3", children: [
6602
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-border" }),
6603
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 text-xs text-muted-foreground", children: date }),
6604
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-border" })
6605
+ ] }),
6606
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: dateMessages.map((message) => {
6607
+ const isCurrentUser = message.senderId === currentUserId;
6608
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6609
+ "div",
6610
+ {
6611
+ className: cn(
6612
+ "flex gap-2",
6613
+ isCurrentUser ? "flex-row-reverse" : "flex-row"
6614
+ ),
6615
+ children: [
6616
+ /* @__PURE__ */ jsxRuntime.jsx(
6617
+ "div",
6618
+ {
6619
+ className: cn(
6620
+ "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-full text-xs font-medium",
6621
+ roleColors[message.senderRole] ?? roleColors.client
6622
+ ),
6623
+ children: (message.senderName ?? message.senderId).split(" ").map((n) => n[0]).join("").slice(0, 2).toUpperCase()
6624
+ }
6625
+ ),
6626
+ /* @__PURE__ */ jsxRuntime.jsxs(
6627
+ "div",
6628
+ {
6629
+ className: cn(
6630
+ "flex max-w-[80%] flex-col",
6631
+ isCurrentUser ? "items-end" : "items-start"
6632
+ ),
6633
+ children: [
6634
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-0.5 flex items-center gap-1.5", children: [
6635
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-foreground", children: message.senderName ?? message.senderId.slice(0, 8) }),
6636
+ /* @__PURE__ */ jsxRuntime.jsx(
6637
+ "span",
6638
+ {
6639
+ className: cn(
6640
+ "rounded px-1 py-0.5 text-[10px] font-medium",
6641
+ roleColors[message.senderRole] ?? ""
6642
+ ),
6643
+ children: getRoleLabel(message.senderRole)
6644
+ }
6645
+ ),
6646
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: formatTime(message.createdAt) })
6647
+ ] }),
6648
+ message.content && /* @__PURE__ */ jsxRuntime.jsx(
6649
+ "div",
6650
+ {
6651
+ className: cn(
6652
+ "rounded-xl px-3 py-2 text-sm",
6653
+ isCurrentUser ? "rounded-br-sm bg-primary text-primary-foreground" : "rounded-bl-sm bg-muted text-foreground"
6654
+ ),
6655
+ children: message.content
6656
+ }
6657
+ ),
6658
+ message.attachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 flex flex-col gap-1", children: message.attachments.map(
6659
+ (att) => att.mimeType.startsWith("image/") ? /* @__PURE__ */ jsxRuntime.jsx(
6660
+ "a",
6661
+ {
6662
+ href: getAttachmentUrl(att.id),
6663
+ target: "_blank",
6664
+ rel: "noopener noreferrer",
6665
+ className: "block max-w-[200px] overflow-hidden rounded-lg border",
6666
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6667
+ "img",
6668
+ {
6669
+ src: getAttachmentUrl(att.id),
6670
+ alt: att.fileName,
6671
+ className: "h-auto w-full object-cover",
6672
+ loading: "lazy"
6673
+ }
6674
+ )
6675
+ },
6676
+ att.id
6677
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
6678
+ "a",
6679
+ {
6680
+ href: getAttachmentUrl(att.id),
6681
+ target: "_blank",
6682
+ rel: "noopener noreferrer",
6683
+ className: "flex items-center gap-2 rounded-lg border bg-muted/50 px-2 py-1.5 text-xs transition-colors hover:bg-muted",
6684
+ children: [
6685
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileIcon, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" }),
6686
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6687
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate font-medium", children: att.fileName }),
6688
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-muted-foreground", children: formatFileSize(att.fileSize) })
6689
+ ] }),
6690
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" })
6691
+ ]
6692
+ },
6693
+ att.id
6694
+ )
6695
+ ) })
6696
+ ]
6697
+ }
6698
+ )
6699
+ ]
6700
+ },
6701
+ message.id
6702
+ );
6703
+ }) })
6704
+ ] }, date)) })
6705
+ ]
6706
+ }
6707
+ ),
6708
+ typingUsers.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 px-3 py-1", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground italic", children: [
6709
+ typingUsers.map((u) => u.userName ?? "Someone").join(", "),
6710
+ " ",
6711
+ typingUsers.length === 1 ? "is" : "are",
6712
+ " typing..."
6713
+ ] }) }),
6714
+ pendingAttachments.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 border-t px-3 py-1.5", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5", children: pendingAttachments.map((att) => /* @__PURE__ */ jsxRuntime.jsxs(
6715
+ "div",
6716
+ {
6717
+ className: "flex items-center gap-1.5 rounded bg-muted px-2 py-0.5 text-xs",
6718
+ children: [
6719
+ att.uploading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileIcon, { className: "h-3 w-3" }),
6720
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "max-w-[100px] truncate", children: att.fileName }),
6721
+ /* @__PURE__ */ jsxRuntime.jsx(
6722
+ "button",
6723
+ {
6724
+ type: "button",
6725
+ onClick: () => removeAttachment(att.id),
6726
+ className: "text-muted-foreground hover:text-foreground",
6727
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3 w-3" })
6728
+ }
6729
+ )
6730
+ ]
6731
+ },
6732
+ att.id
6733
+ )) }) }),
6734
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 border-t p-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
6735
+ "form",
6736
+ {
6737
+ onSubmit: (e) => {
6738
+ e.preventDefault();
6739
+ handleSendMessage();
6740
+ },
6741
+ className: "flex gap-2",
6742
+ children: [
6743
+ /* @__PURE__ */ jsxRuntime.jsx(
6744
+ "input",
6745
+ {
6746
+ ref: fileInputRef,
6747
+ type: "file",
6748
+ multiple: true,
6749
+ className: "hidden",
6750
+ onChange: handleFileSelect
6751
+ }
6752
+ ),
6753
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6754
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6755
+ Button,
6756
+ {
6757
+ type: "button",
6758
+ variant: "ghost",
6759
+ size: "icon",
6760
+ className: "h-9 w-9 flex-shrink-0",
6761
+ onClick: () => fileInputRef.current?.click(),
6762
+ "aria-label": labels.attachFile ?? "Attach file",
6763
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-4 w-4" })
6764
+ }
6765
+ ) }),
6766
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { children: labels.attachFile ?? "Attach file" })
6767
+ ] }) }),
6768
+ /* @__PURE__ */ jsxRuntime.jsx(
6769
+ "textarea",
6770
+ {
6771
+ ref: inputRef,
6772
+ value: newMessage,
6773
+ onChange: (e) => {
6774
+ setNewMessage(e.target.value);
6775
+ handleTyping();
6776
+ },
6777
+ onKeyDown: handleKeyDown,
6778
+ placeholder: labels.placeholder ?? "Type a message...",
6779
+ className: "h-9 min-h-[36px] max-h-[100px] flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
6780
+ rows: 1
6781
+ }
6782
+ ),
6783
+ /* @__PURE__ */ jsxRuntime.jsx(
6784
+ Button,
6785
+ {
6786
+ type: "submit",
6787
+ size: "sm",
6788
+ className: "h-9",
6789
+ disabled: !newMessage.trim() && pendingAttachments.filter((a) => !a.uploading).length === 0,
6790
+ "aria-label": labels.send ?? "Send",
6791
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-4 w-4", "aria-hidden": "true" })
6792
+ }
6793
+ )
6794
+ ]
6795
+ }
6796
+ ) })
6797
+ ] })
6798
+ ] });
6799
+ }
6800
+ var roomTypeLabels = {
6801
+ client_agent: "Client \u2194 Agent",
6802
+ dealer_agent: "Dealer \u2194 Agent"
6803
+ };
6804
+ function ChatSidebar({
6805
+ currentUserId,
6806
+ config,
6807
+ labels = {},
6808
+ roleLabel,
6809
+ roomTypeLabel,
6810
+ className
6811
+ }) {
6812
+ const {
6813
+ isOpen,
6814
+ view,
6815
+ conversations,
6816
+ isLoading,
6817
+ activeCaseId,
6818
+ activeRoomId,
6819
+ close,
6820
+ selectRoom,
6821
+ back
6822
+ } = useChatSidebar();
6823
+ if (!isOpen) return null;
6824
+ const getRoomTypeLabel = (type) => {
6825
+ if (roomTypeLabel) return roomTypeLabel(type);
6826
+ return roomTypeLabels[type] ?? type;
6827
+ };
6828
+ const activeConversation = activeCaseId ? conversations.find((c) => c.caseId === activeCaseId) : null;
6829
+ return /* @__PURE__ */ jsxRuntime.jsx(
6830
+ "div",
6831
+ {
6832
+ className: cn(
6833
+ "flex h-full w-[360px] flex-col border-l bg-background xl:w-[400px]",
6834
+ className
6835
+ ),
6836
+ children: view === "list" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6837
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
6838
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
6839
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-4 w-4 text-primary" }),
6840
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: labels.title ?? "Chat" })
6841
+ ] }),
6842
+ /* @__PURE__ */ jsxRuntime.jsx(
6843
+ Button,
6844
+ {
6845
+ variant: "ghost",
6846
+ size: "icon",
6847
+ className: "h-7 w-7",
6848
+ onClick: close,
6849
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
6850
+ }
6851
+ )
6852
+ ] }),
6853
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "flex-1", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) }) : conversations.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center px-4", children: [
6854
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "mb-3 h-8 w-8 text-muted-foreground/50" }),
6855
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.noConversations ?? "No conversations yet" })
6856
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: conversations.map((conv) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-2", children: [
6857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-2 pt-3 pb-1", children: [
6858
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
6859
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-foreground", children: conv.caseNumber }),
6860
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: conv.caseStatus })
6861
+ ] }),
6862
+ conv.clientName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground truncate", children: conv.clientName })
6863
+ ] }),
6864
+ conv.rooms.map((room) => /* @__PURE__ */ jsxRuntime.jsxs(
6865
+ "button",
6866
+ {
6867
+ type: "button",
6868
+ onClick: () => selectRoom(room.id, conv.caseId),
6869
+ className: cn(
6870
+ "flex w-full items-center justify-between rounded-md px-2 py-2 text-left transition-colors hover:bg-accent",
6871
+ activeRoomId === room.id && "bg-accent"
6872
+ ),
6873
+ children: [
6874
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
6875
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" }),
6876
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm truncate", children: getRoomTypeLabel(room.type) })
6877
+ ] }),
6878
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 flex-shrink-0", children: [
6879
+ room.lastMessageAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: formatRelativeTime(room.lastMessageAt) }),
6880
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground" })
6881
+ ] })
6882
+ ]
6883
+ },
6884
+ room.id
6885
+ ))
6886
+ ] }, conv.caseId)) }) })
6887
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6888
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
6889
+ /* @__PURE__ */ jsxRuntime.jsx(
6890
+ Button,
6891
+ {
6892
+ variant: "ghost",
6893
+ size: "icon",
6894
+ className: "h-7 w-7",
6895
+ onClick: back,
6896
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-4 w-4" })
6897
+ }
6898
+ ),
6899
+ activeConversation && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6900
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium truncate", children: activeConversation.caseNumber }),
6901
+ activeConversation.clientName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground truncate", children: activeConversation.clientName })
6902
+ ] }),
6903
+ /* @__PURE__ */ jsxRuntime.jsx(
6904
+ Button,
6905
+ {
6906
+ variant: "ghost",
6907
+ size: "icon",
6908
+ className: "h-7 w-7",
6909
+ onClick: close,
6910
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
6911
+ }
6912
+ )
6913
+ ] }),
6914
+ activeRoomId && /* @__PURE__ */ jsxRuntime.jsx(
6915
+ ChatRoomView,
6916
+ {
6917
+ roomId: activeRoomId,
6918
+ currentUserId,
6919
+ config,
6920
+ labels,
6921
+ roleLabel,
6922
+ className: "flex-1"
6923
+ }
6924
+ )
6925
+ ] })
6926
+ }
6927
+ );
6928
+ }
6929
+ function formatRelativeTime(dateStr) {
6930
+ const date = new Date(dateStr);
6931
+ const now = /* @__PURE__ */ new Date();
6932
+ const diffMs = now.getTime() - date.getTime();
6933
+ const diffMin = Math.floor(diffMs / 6e4);
6934
+ if (diffMin < 1) return "now";
6935
+ if (diffMin < 60) return `${diffMin}m`;
6936
+ const diffH = Math.floor(diffMin / 60);
6937
+ if (diffH < 24) return `${diffH}h`;
6938
+ const diffD = Math.floor(diffH / 24);
6939
+ if (diffD < 7) return `${diffD}d`;
6940
+ return date.toLocaleDateString([], { month: "short", day: "numeric" });
6941
+ }
5411
6942
 
5412
6943
  exports.AvatarEditor = AvatarEditor;
5413
6944
  exports.AvatarEditorDialog = AvatarEditorDialog;
5414
6945
  exports.Button = Button;
6946
+ exports.CalendarContext = CalendarContext;
6947
+ exports.CalendarSubscribeButton = CalendarSubscribeButton;
6948
+ exports.CalendarView = CalendarView;
6949
+ exports.CalendarWidget = CalendarWidget;
6950
+ exports.ChatRoomView = ChatRoomView;
6951
+ exports.ChatSidebar = ChatSidebar;
6952
+ exports.ChatSidebarContext = ChatSidebarContext;
6953
+ exports.ChatSidebarProvider = ChatSidebarProvider;
5415
6954
  exports.Dialog = Dialog;
5416
6955
  exports.DialogClose = DialogClose;
5417
6956
  exports.DialogContent = DialogContent;
@@ -5447,8 +6986,11 @@ exports.DropdownMenuSub = DropdownMenuSub;
5447
6986
  exports.DropdownMenuSubContent = DropdownMenuSubContent;
5448
6987
  exports.DropdownMenuSubTrigger = DropdownMenuSubTrigger;
5449
6988
  exports.DropdownMenuTrigger = DropdownMenuTrigger;
6989
+ exports.EventDialog = EventDialog;
6990
+ exports.EventRsvpBadge = EventRsvpBadge;
5450
6991
  exports.LanguageContext = LanguageContext;
5451
6992
  exports.LanguageSwitcher = LanguageSwitcher;
6993
+ exports.MiniCalendar = MiniCalendar;
5452
6994
  exports.NotificationsContext = NotificationsContext;
5453
6995
  exports.NotificationsWidget = NotificationsWidget;
5454
6996
  exports.Popover = Popover;
@@ -5464,10 +7006,14 @@ exports.Tooltip = Tooltip;
5464
7006
  exports.TooltipContent = TooltipContent;
5465
7007
  exports.TooltipProvider = TooltipProvider;
5466
7008
  exports.TooltipTrigger = TooltipTrigger;
7009
+ exports.UpcomingEvents = UpcomingEvents;
5467
7010
  exports.buttonVariants = buttonVariants;
5468
7011
  exports.cn = cn;
5469
7012
  exports.defaultLanguages = defaultLanguages;
5470
7013
  exports.toggleVariants = toggleVariants;
7014
+ exports.useCalendarContext = useCalendarContext;
7015
+ exports.useChatRoom = useChatRoom;
7016
+ exports.useChatSidebar = useChatSidebar;
5471
7017
  exports.useLanguageContext = useLanguageContext;
5472
7018
  exports.useNotificationsContext = useNotificationsContext;
5473
7019
  //# sourceMappingURL=index.cjs.map