@wealthx/shadcn 1.2.1 → 1.3.0

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 (247) hide show
  1. package/.turbo/turbo-build.log +203 -150
  2. package/CHANGELOG.md +29 -0
  3. package/dist/{chunk-4Y6R4WEC.mjs → chunk-2A5RRQGG.mjs} +9 -22
  4. package/dist/{chunk-TS2ZX2VS.mjs → chunk-2UM72RJ7.mjs} +11 -15
  5. package/dist/{chunk-A56YQQHG.mjs → chunk-3NCUZIFP.mjs} +2 -2
  6. package/dist/chunk-3OYFOX3X.mjs +79 -0
  7. package/dist/{chunk-RP3SQYA3.mjs → chunk-3TTACBDP.mjs} +9 -4
  8. package/dist/chunk-4GAWMKMI.mjs +710 -0
  9. package/dist/{chunk-SYOD63OZ.mjs → chunk-5FQIKDKP.mjs} +6 -6
  10. package/dist/{chunk-K3JYD4IU.mjs → chunk-5IS7G74I.mjs} +11 -4
  11. package/dist/chunk-6AW4KJHE.mjs +235 -0
  12. package/dist/chunk-6CR5N2JW.mjs +302 -0
  13. package/dist/{chunk-XIRTEFKH.mjs → chunk-6DZEXFNB.mjs} +36 -8
  14. package/dist/chunk-6O6KD7CE.mjs +271 -0
  15. package/dist/chunk-7PV3IWCN.mjs +33 -0
  16. package/dist/{chunk-SPJ5KXW7.mjs → chunk-7S5AESZO.mjs} +5 -5
  17. package/dist/{chunk-RYCLWMZ7.mjs → chunk-ABFDMHOR.mjs} +9 -7
  18. package/dist/{chunk-SWGT756Z.mjs → chunk-AMQZRHEZ.mjs} +10 -4
  19. package/dist/{chunk-WOEHFRGB.mjs → chunk-BDYZCBRT.mjs} +4 -4
  20. package/dist/{chunk-WAZD7NFU.mjs → chunk-BKNFWEH2.mjs} +6 -6
  21. package/dist/{chunk-CLIN5525.mjs → chunk-C7CQJNMR.mjs} +1 -1
  22. package/dist/{chunk-D4ILTPOG.mjs → chunk-CFMQP5QS.mjs} +5 -4
  23. package/dist/{chunk-VPBN3WOO.mjs → chunk-DGHAXJBN.mjs} +9 -7
  24. package/dist/chunk-DOEO3CDL.mjs +27 -0
  25. package/dist/{chunk-KUDCQ4FI.mjs → chunk-DUJTAXMH.mjs} +9 -4
  26. package/dist/{chunk-GGM2UYGG.mjs → chunk-EBXQWIYG.mjs} +10 -4
  27. package/dist/{chunk-PMB3A7V3.mjs → chunk-EI5F6FMT.mjs} +1 -1
  28. package/dist/chunk-EWRB4PAD.mjs +468 -0
  29. package/dist/chunk-FAKPBKLT.mjs +253 -0
  30. package/dist/chunk-FNQXOAYJ.mjs +169 -0
  31. package/dist/{chunk-4CX4SBRO.mjs → chunk-GHC7LLUX.mjs} +14 -5
  32. package/dist/chunk-HBZLGDIN.mjs +507 -0
  33. package/dist/{chunk-SIZMLSRU.mjs → chunk-HISNT2MG.mjs} +8 -6
  34. package/dist/{chunk-PR6V5XKM.mjs → chunk-HVY6KCCF.mjs} +7 -4
  35. package/dist/chunk-I3RZS7V2.mjs +136 -0
  36. package/dist/chunk-IAE3F7DR.mjs +1962 -0
  37. package/dist/{chunk-ZRO5JO3H.mjs → chunk-IHMFS7NZ.mjs} +81 -84
  38. package/dist/{chunk-PCPLO5HT.mjs → chunk-IOJRDS6V.mjs} +96 -14
  39. package/dist/{chunk-LHYCMLVA.mjs → chunk-JKGDCQTZ.mjs} +11 -4
  40. package/dist/{chunk-H45TKD34.mjs → chunk-JMHR3YGZ.mjs} +1 -1
  41. package/dist/{chunk-4MN6UQHG.mjs → chunk-K5A5L6T2.mjs} +17 -39
  42. package/dist/{chunk-CSDO6VBW.mjs → chunk-LBMRIB3G.mjs} +10 -10
  43. package/dist/chunk-LV35NGVG.mjs +272 -0
  44. package/dist/{chunk-FZIXGLMV.mjs → chunk-M3FV7LOK.mjs} +5 -12
  45. package/dist/{chunk-FMAXJ2SI.mjs → chunk-MBON7YRJ.mjs} +1 -1
  46. package/dist/chunk-MIZQHHUO.mjs +441 -0
  47. package/dist/chunk-MN5NYQCL.mjs +29 -0
  48. package/dist/chunk-NL3ZO62D.mjs +31 -0
  49. package/dist/{chunk-Q76O3RIQ.mjs → chunk-NMOI6CQD.mjs} +1 -1
  50. package/dist/{chunk-P6AM5V7O.mjs → chunk-OODBHKG7.mjs} +1 -1
  51. package/dist/chunk-PBL4OQV2.mjs +283 -0
  52. package/dist/{chunk-3WMX6KWS.mjs → chunk-PU4YZQXV.mjs} +11 -12
  53. package/dist/chunk-QMY3AZJH.mjs +80 -0
  54. package/dist/{chunk-BL3DXM2X.mjs → chunk-QZ4RE6NA.mjs} +11 -4
  55. package/dist/{chunk-VACKZOMY.mjs → chunk-R3VSPKNP.mjs} +3 -3
  56. package/dist/{chunk-OPNQAVVH.mjs → chunk-RJI6GKVF.mjs} +8 -6
  57. package/dist/{chunk-WG6JGJXB.mjs → chunk-T4BJLT57.mjs} +1 -1
  58. package/dist/chunk-U4NDAF2P.mjs +207 -0
  59. package/dist/{chunk-DOH3EHX7.mjs → chunk-U5X52X37.mjs} +1 -1
  60. package/dist/chunk-UMTOX62O.mjs +415 -0
  61. package/dist/{chunk-7MMXNK3C.mjs → chunk-VLARHE5V.mjs} +8 -6
  62. package/dist/{chunk-2I5S2AMY.mjs → chunk-XREGSKX3.mjs} +2 -2
  63. package/dist/{chunk-JNQORUPP.mjs → chunk-YJG55G2H.mjs} +14 -11
  64. package/dist/chunk-ZC45IGZO.mjs +388 -0
  65. package/dist/components/ui/add-column-modal.js +42 -14
  66. package/dist/components/ui/add-column-modal.mjs +4 -4
  67. package/dist/components/ui/add-lead-modal.js +42 -11
  68. package/dist/components/ui/add-lead-modal.mjs +3 -3
  69. package/dist/components/ui/advisor-card.js +497 -0
  70. package/dist/components/ui/advisor-card.mjs +13 -0
  71. package/dist/components/ui/ai-assistant-drawer.js +11 -10
  72. package/dist/components/ui/ai-assistant-drawer.mjs +3 -3
  73. package/dist/components/ui/alert-dialog.js +2 -2
  74. package/dist/components/ui/alert-dialog.mjs +2 -2
  75. package/dist/components/ui/appointment-action-dialogs.js +1160 -0
  76. package/dist/components/ui/appointment-action-dialogs.mjs +23 -0
  77. package/dist/components/ui/appointment-availability-settings.js +1590 -0
  78. package/dist/components/ui/appointment-availability-settings.mjs +23 -0
  79. package/dist/components/ui/appointment-book-dialog.js +1744 -0
  80. package/dist/components/ui/appointment-book-dialog.mjs +27 -0
  81. package/dist/components/ui/appointment-calendar-view.js +833 -0
  82. package/dist/components/ui/appointment-calendar-view.mjs +14 -0
  83. package/dist/components/ui/appointment-detail-sheet.js +1517 -0
  84. package/dist/components/ui/appointment-detail-sheet.mjs +24 -0
  85. package/dist/components/ui/appointment-gmail-connect.js +467 -0
  86. package/dist/components/ui/appointment-gmail-connect.mjs +14 -0
  87. package/dist/components/ui/appointment-mini-card.js +345 -0
  88. package/dist/components/ui/appointment-mini-card.mjs +11 -0
  89. package/dist/components/ui/appointment-time-slot-picker.js +311 -0
  90. package/dist/components/ui/appointment-time-slot-picker.mjs +13 -0
  91. package/dist/components/ui/appointment-upcoming-card.js +1268 -0
  92. package/dist/components/ui/appointment-upcoming-card.mjs +21 -0
  93. package/dist/components/ui/backoffice-alert-history-chart.js +11 -5
  94. package/dist/components/ui/backoffice-alert-history-chart.mjs +5 -4
  95. package/dist/components/ui/backoffice-alerts-chart.js +786 -0
  96. package/dist/components/ui/backoffice-alerts-chart.mjs +19 -0
  97. package/dist/components/ui/backoffice-connections-chart.js +817 -0
  98. package/dist/components/ui/backoffice-connections-chart.mjs +19 -0
  99. package/dist/components/ui/backoffice-contact-history-chart.js +11 -5
  100. package/dist/components/ui/backoffice-contact-history-chart.mjs +5 -4
  101. package/dist/components/ui/badge.js +6 -6
  102. package/dist/components/ui/badge.mjs +1 -1
  103. package/dist/components/ui/borrowing-capacity-line-chart.js +30 -21
  104. package/dist/components/ui/borrowing-capacity-line-chart.mjs +5 -4
  105. package/dist/components/ui/button.js +2 -2
  106. package/dist/components/ui/button.mjs +1 -1
  107. package/dist/components/ui/calendar.js +2 -2
  108. package/dist/components/ui/calendar.mjs +2 -2
  109. package/dist/components/ui/card.js +1 -1
  110. package/dist/components/ui/card.mjs +1 -1
  111. package/dist/components/ui/cash-balance-line-chart.js +31 -23
  112. package/dist/components/ui/cash-balance-line-chart.mjs +5 -4
  113. package/dist/components/ui/cashflow-bar-chart.js +12 -5
  114. package/dist/components/ui/cashflow-bar-chart.mjs +5 -4
  115. package/dist/components/ui/chip.js +97 -18
  116. package/dist/components/ui/chip.mjs +3 -2
  117. package/dist/components/ui/color-picker.js +547 -0
  118. package/dist/components/ui/color-picker.mjs +24 -0
  119. package/dist/components/ui/data-table.js +182 -129
  120. package/dist/components/ui/data-table.mjs +3 -2
  121. package/dist/components/ui/date-picker.js +48 -27
  122. package/dist/components/ui/date-picker.mjs +4 -3
  123. package/dist/components/ui/dialog.js +37 -9
  124. package/dist/components/ui/dialog.mjs +2 -2
  125. package/dist/components/ui/expense-bar-chart.js +12 -5
  126. package/dist/components/ui/expense-bar-chart.mjs +5 -4
  127. package/dist/components/ui/field.mjs +2 -2
  128. package/dist/components/ui/financial-cards.js +322 -155
  129. package/dist/components/ui/financial-cards.mjs +5 -3
  130. package/dist/components/ui/financial-drawers.js +2 -2
  131. package/dist/components/ui/financial-drawers.mjs +3 -3
  132. package/dist/components/ui/financial-sections.js +14 -10
  133. package/dist/components/ui/financial-sections.mjs +6 -5
  134. package/dist/components/ui/form-primitives.js +4 -4
  135. package/dist/components/ui/form-primitives.mjs +3 -3
  136. package/dist/components/ui/income-bar-chart.js +12 -5
  137. package/dist/components/ui/income-bar-chart.mjs +5 -4
  138. package/dist/components/ui/input-group.js +2 -2
  139. package/dist/components/ui/input-group.mjs +2 -2
  140. package/dist/components/ui/kanban-column.js +52 -44
  141. package/dist/components/ui/kanban-column.mjs +7 -5
  142. package/dist/components/ui/opportunity-card.js +52 -44
  143. package/dist/components/ui/opportunity-card.mjs +6 -4
  144. package/dist/components/ui/opportunity-edit-modals.js +1371 -1267
  145. package/dist/components/ui/opportunity-edit-modals.mjs +10 -10
  146. package/dist/components/ui/opportunity-summary-tab.js +2748 -2161
  147. package/dist/components/ui/opportunity-summary-tab.mjs +16 -16
  148. package/dist/components/ui/page-header.js +92 -0
  149. package/dist/components/ui/page-header.mjs +8 -0
  150. package/dist/components/ui/page-top-bar.js +88 -0
  151. package/dist/components/ui/page-top-bar.mjs +8 -0
  152. package/dist/components/ui/pagination.js +303 -19
  153. package/dist/components/ui/pagination.mjs +11 -4
  154. package/dist/components/ui/pipeline-board.js +209 -195
  155. package/dist/components/ui/pipeline-board.mjs +10 -8
  156. package/dist/components/ui/pipeline-dialogs.js +118 -69
  157. package/dist/components/ui/pipeline-dialogs.mjs +8 -7
  158. package/dist/components/ui/pipeline-primitives.js +6 -6
  159. package/dist/components/ui/pipeline-primitives.mjs +2 -2
  160. package/dist/components/ui/property-cashflow-doughnut-chart.js +14 -12
  161. package/dist/components/ui/property-cashflow-doughnut-chart.mjs +5 -4
  162. package/dist/components/ui/property-debt-equity-doughnut-chart.js +14 -12
  163. package/dist/components/ui/property-debt-equity-doughnut-chart.mjs +5 -4
  164. package/dist/components/ui/property-mobile-estimate-line-chart.js +16 -14
  165. package/dist/components/ui/property-mobile-estimate-line-chart.mjs +5 -4
  166. package/dist/components/ui/sidebar-nav.js +679 -0
  167. package/dist/components/ui/sidebar-nav.mjs +14 -0
  168. package/dist/components/ui/stage-timeline.js +6 -6
  169. package/dist/components/ui/stage-timeline.mjs +3 -3
  170. package/dist/components/ui/stepper.js +283 -0
  171. package/dist/components/ui/stepper.mjs +18 -0
  172. package/dist/components/ui/toggle-group.js +4 -4
  173. package/dist/components/ui/toggle-group.mjs +2 -2
  174. package/dist/components/ui/toggle.js +4 -4
  175. package/dist/components/ui/toggle.mjs +1 -1
  176. package/dist/components/ui/transactions-expense-categories-doughnut-chart.js +18 -16
  177. package/dist/components/ui/transactions-expense-categories-doughnut-chart.mjs +5 -4
  178. package/dist/components/ui/transactions-income-expense-bar-chart.js +28 -12
  179. package/dist/components/ui/transactions-income-expense-bar-chart.mjs +5 -4
  180. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.js +18 -16
  181. package/dist/components/ui/transactions-liabilities-breakdown-doughnut-chart.mjs +5 -4
  182. package/dist/index.js +12927 -8522
  183. package/dist/index.mjs +288 -190
  184. package/dist/lib/typography.js +10 -10
  185. package/dist/lib/typography.mjs +1 -1
  186. package/dist/styles.css +1 -1
  187. package/package.json +86 -1
  188. package/src/components/index.tsx +146 -0
  189. package/src/components/ui/add-column-modal.tsx +7 -7
  190. package/src/components/ui/add-lead-modal.tsx +6 -3
  191. package/src/components/ui/advisor-card.tsx +227 -0
  192. package/src/components/ui/ai-assistant-drawer.tsx +4 -3
  193. package/src/components/ui/appointment-action-dialogs.tsx +297 -0
  194. package/src/components/ui/appointment-availability-settings.tsx +645 -0
  195. package/src/components/ui/appointment-book-dialog.tsx +618 -0
  196. package/src/components/ui/appointment-calendar-view.tsx +510 -0
  197. package/src/components/ui/appointment-detail-sheet.tsx +415 -0
  198. package/src/components/ui/appointment-gmail-connect.tsx +188 -0
  199. package/src/components/ui/appointment-mini-card.tsx +104 -0
  200. package/src/components/ui/appointment-time-slot-picker.tsx +123 -0
  201. package/src/components/ui/appointment-upcoming-card.tsx +635 -0
  202. package/src/components/ui/backoffice-alert-history-chart.tsx +10 -2
  203. package/src/components/ui/backoffice-alerts-chart.tsx +312 -0
  204. package/src/components/ui/backoffice-connections-chart.tsx +339 -0
  205. package/src/components/ui/backoffice-contact-history-chart.tsx +10 -2
  206. package/src/components/ui/badge.tsx +12 -6
  207. package/src/components/ui/borrowing-capacity-line-chart.tsx +4 -11
  208. package/src/components/ui/button.tsx +2 -2
  209. package/src/components/ui/card.tsx +1 -1
  210. package/src/components/ui/cash-balance-line-chart.tsx +4 -23
  211. package/src/components/ui/cashflow-bar-chart.tsx +9 -2
  212. package/src/components/ui/chart-shared.tsx +4 -11
  213. package/src/components/ui/chip.tsx +23 -19
  214. package/src/components/ui/color-picker.tsx +309 -0
  215. package/src/components/ui/data-table.tsx +117 -83
  216. package/src/components/ui/date-picker.tsx +42 -37
  217. package/src/components/ui/dialog.tsx +72 -6
  218. package/src/components/ui/expense-bar-chart.tsx +11 -2
  219. package/src/components/ui/financial-cards.tsx +99 -10
  220. package/src/components/ui/income-bar-chart.tsx +11 -2
  221. package/src/components/ui/opportunity-card.tsx +10 -39
  222. package/src/components/ui/opportunity-edit-modals.tsx +98 -36
  223. package/src/components/ui/opportunity-summary-tab.tsx +548 -232
  224. package/src/components/ui/page-header.tsx +57 -0
  225. package/src/components/ui/page-top-bar.tsx +48 -0
  226. package/src/components/ui/pagination.tsx +171 -22
  227. package/src/components/ui/pipeline-board.tsx +12 -5
  228. package/src/components/ui/property-cashflow-doughnut-chart.tsx +3 -1
  229. package/src/components/ui/property-debt-equity-doughnut-chart.tsx +3 -1
  230. package/src/components/ui/property-mobile-estimate-line-chart.tsx +3 -1
  231. package/src/components/ui/sidebar-nav.tsx +516 -0
  232. package/src/components/ui/stepper.tsx +347 -0
  233. package/src/components/ui/toggle.tsx +4 -4
  234. package/src/components/ui/transactions-expense-categories-doughnut-chart.tsx +3 -1
  235. package/src/components/ui/transactions-income-expense-bar-chart.tsx +12 -9
  236. package/src/components/ui/transactions-liabilities-breakdown-doughnut-chart.tsx +3 -1
  237. package/src/lib/format-currency.ts +44 -0
  238. package/src/lib/format-date.ts +50 -0
  239. package/src/lib/opportunity-constants.ts +12 -0
  240. package/src/lib/typography.ts +11 -11
  241. package/src/styles/globals.css +36 -34
  242. package/src/styles/styles-css.ts +1 -1
  243. package/tsup.config.ts +17 -0
  244. package/dist/chunk-PG6K5XEC.mjs +0 -475
  245. package/dist/chunk-WA6O6EUR.mjs +0 -1885
  246. package/dist/chunk-WNGWBVLV.mjs +0 -148
  247. package/dist/{chunk-LLVQKSU3.mjs → chunk-GD4BJDJR.mjs} +3 -3
@@ -0,0 +1,415 @@
1
+ import React from "react";
2
+ import { Avatar, AvatarFallback } from "./avatar";
3
+ import { Badge } from "./badge";
4
+ import { Button } from "./button";
5
+ import { Separator } from "./separator";
6
+ import { Sheet, SheetContent } from "./sheet";
7
+ import {
8
+ AppointmentConfirmAction,
9
+ AppointmentConfirmDialog,
10
+ AppointmentRescheduleDialog,
11
+ } from "./appointment-action-dialogs";
12
+ import type {
13
+ AppointmentMeetingFormat,
14
+ AppointmentStatus,
15
+ AppointmentTimeSlot,
16
+ } from "./appointment-time-slot-picker";
17
+ import {
18
+ AlertCircle,
19
+ Calendar,
20
+ CalendarClock,
21
+ Check,
22
+ CircleUser,
23
+ Clock,
24
+ FileText,
25
+ Mail,
26
+ MapPin,
27
+ Phone,
28
+ RefreshCw,
29
+ Users,
30
+ Video,
31
+ X,
32
+ } from "lucide-react";
33
+
34
+ // Re-export so consumers who import these types from this module still work
35
+ export type {
36
+ AppointmentStatus,
37
+ AppointmentMeetingFormat,
38
+ } from "./appointment-time-slot-picker";
39
+
40
+ export interface AppointmentDetailItem {
41
+ id: string;
42
+ status: AppointmentStatus;
43
+ clientName: string;
44
+ clientAvatarInitials: string;
45
+ /** Client ID — used to pre-select client when rebooking after cancellation */
46
+ clientId?: string;
47
+ date: string;
48
+ timeStart: string;
49
+ timeEnd: string;
50
+ notes?: string;
51
+ /** Reason provided when the appointment was cancelled */
52
+ cancelReason?: string;
53
+ /** How the meeting is conducted */
54
+ meetingFormat?: AppointmentMeetingFormat;
55
+ /** Formatted location string — shown for offline meetings */
56
+ meetingLocation?: string;
57
+ }
58
+
59
+ export type LoanApplicationStatus =
60
+ | "finished"
61
+ | "in-progress"
62
+ | "sent-request"
63
+ | "not-start";
64
+
65
+ /** Extra client profile fields not stored on the appointment itself */
66
+ export interface AppointmentClientProfile {
67
+ phone?: string;
68
+ email?: string;
69
+ accountType?: "Individual" | "Joint";
70
+ loanApplicationStatus?: LoanApplicationStatus;
71
+ }
72
+
73
+ export interface AppointmentDetailSheetProps {
74
+ appointment: AppointmentDetailItem | undefined;
75
+ open: boolean;
76
+ onOpenChange: (v: boolean) => void;
77
+ clientProfile?: AppointmentClientProfile;
78
+ amSlots: AppointmentTimeSlot[];
79
+ pmSlots: AppointmentTimeSlot[];
80
+ onAccept?: (id: string) => void;
81
+ onDecline?: (id: string) => void;
82
+ onReschedule?: (
83
+ id: string,
84
+ date: Date,
85
+ slot: AppointmentTimeSlot,
86
+ note: string,
87
+ ) => void;
88
+ /** Called when "Book a New Appointment" is clicked on a cancelled appointment */
89
+ onBookNew?: (clientId?: string) => void;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Status config
94
+ // ---------------------------------------------------------------------------
95
+
96
+ const STATUS_CONFIG: Record<
97
+ AppointmentStatus,
98
+ {
99
+ variant: "warning" | "success" | "destructive" | "info";
100
+ label: string;
101
+ icon: React.ReactNode;
102
+ }
103
+ > = {
104
+ pending: {
105
+ variant: "warning",
106
+ label: "Pending",
107
+ icon: <CalendarClock size={12} />,
108
+ },
109
+ confirmed: {
110
+ variant: "success",
111
+ label: "Confirmed",
112
+ icon: <Check size={12} />,
113
+ },
114
+ cancelled: {
115
+ variant: "destructive",
116
+ label: "Cancelled",
117
+ icon: <X size={12} />,
118
+ },
119
+ rescheduled: {
120
+ variant: "info",
121
+ label: "Rescheduled",
122
+ icon: <RefreshCw size={12} />,
123
+ },
124
+ };
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Component
128
+ // ---------------------------------------------------------------------------
129
+
130
+ export function AppointmentDetailSheet({
131
+ appointment,
132
+ open,
133
+ onOpenChange,
134
+ clientProfile,
135
+ amSlots,
136
+ pmSlots,
137
+ onAccept,
138
+ onDecline,
139
+ onReschedule,
140
+ onBookNew,
141
+ }: AppointmentDetailSheetProps) {
142
+ const [confirmAction, setConfirmAction] =
143
+ React.useState<AppointmentConfirmAction | null>(null);
144
+ const [rescheduleOpen, setRescheduleOpen] = React.useState(false);
145
+
146
+ if (!appointment) return null;
147
+
148
+ const { variant, label, icon } = STATUS_CONFIG[appointment.status];
149
+ const isCancelled = appointment.status === "cancelled";
150
+ const isConfirmed = appointment.status === "confirmed";
151
+
152
+ return (
153
+ <>
154
+ <Sheet open={open} onOpenChange={onOpenChange}>
155
+ <SheetContent
156
+ side="right"
157
+ className="flex w-[440px] flex-col gap-0 overflow-y-auto p-0"
158
+ >
159
+ {/* ── Header: avatar + name/badge + action buttons ── */}
160
+ <div className="border-b border-border px-6 py-5">
161
+ <div className="flex items-start gap-4">
162
+ <Avatar className="h-12 w-12 shrink-0">
163
+ <AvatarFallback className="text-base">
164
+ {appointment.clientAvatarInitials}
165
+ </AvatarFallback>
166
+ </Avatar>
167
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
168
+ <p className="text-lg font-semibold leading-tight">
169
+ {appointment.clientName}
170
+ </p>
171
+ <Badge variant={variant} className="w-fit">
172
+ {icon}
173
+ {label}
174
+ </Badge>
175
+ </div>
176
+ </div>
177
+
178
+ {/* Action buttons */}
179
+ <div className="mt-4 flex gap-2">
180
+ {isCancelled ? (
181
+ <Button
182
+ size="sm"
183
+ className="w-full"
184
+ onClick={() => onBookNew?.(appointment.clientId)}
185
+ >
186
+ Book a New Appointment
187
+ </Button>
188
+ ) : (
189
+ <>
190
+ <Button
191
+ size="sm"
192
+ disabled={isConfirmed}
193
+ onClick={() => setConfirmAction("accept")}
194
+ className="flex-1 gap-1.5"
195
+ >
196
+ <Check className="h-3.5 w-3.5" />
197
+ Accept
198
+ </Button>
199
+ <Button
200
+ size="sm"
201
+ variant="ghost"
202
+ onClick={() => setConfirmAction("decline")}
203
+ className="flex-1 gap-1.5 text-destructive hover:text-destructive"
204
+ >
205
+ <X className="h-3.5 w-3.5" />
206
+ Decline
207
+ </Button>
208
+ <Button
209
+ size="sm"
210
+ variant="outline"
211
+ onClick={() => setRescheduleOpen(true)}
212
+ className="flex-1 gap-1.5"
213
+ >
214
+ <RefreshCw className="h-3.5 w-3.5" />
215
+ Reschedule
216
+ </Button>
217
+ </>
218
+ )}
219
+ </div>
220
+ </div>
221
+
222
+ {/* ── Appointment details ── */}
223
+ <div className="flex flex-col gap-4 px-6 py-5">
224
+ <div className="flex items-center gap-3 text-sm">
225
+ <Calendar className="h-4 w-4 shrink-0 text-muted-foreground" />
226
+ <span className="font-medium">{appointment.date}</span>
227
+ </div>
228
+ <div className="flex items-center gap-3 text-sm">
229
+ <Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
230
+ <span className="font-medium">
231
+ {appointment.timeStart} – {appointment.timeEnd}
232
+ </span>
233
+ </div>
234
+
235
+ {/* Meeting format — for offline, location is shown inline to avoid a second MapPin */}
236
+ {appointment.meetingFormat && (
237
+ <div className="flex items-start gap-3 text-sm">
238
+ {appointment.meetingFormat === "call" && (
239
+ <Phone className="h-4 w-4 shrink-0 text-muted-foreground" />
240
+ )}
241
+ {appointment.meetingFormat === "google-meet" && (
242
+ <Video className="h-4 w-4 shrink-0 text-muted-foreground" />
243
+ )}
244
+ {appointment.meetingFormat === "offline" && (
245
+ <MapPin className="mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
246
+ )}
247
+ <div className="flex flex-col gap-0.5">
248
+ <span className="font-medium">
249
+ {appointment.meetingFormat === "call" && "Phone Call"}
250
+ {appointment.meetingFormat === "google-meet" &&
251
+ "Google Meet"}
252
+ {appointment.meetingFormat === "offline" && "In Person"}
253
+ </span>
254
+ {/* Location shown below format label — only for offline, avoids duplicate MapPin */}
255
+ {appointment.meetingFormat === "offline" &&
256
+ appointment.meetingLocation && (
257
+ <span className="text-muted-foreground">
258
+ {appointment.meetingLocation}
259
+ </span>
260
+ )}
261
+ </div>
262
+ </div>
263
+ )}
264
+ </div>
265
+
266
+ {/* ── Client profile ── */}
267
+ {clientProfile &&
268
+ (clientProfile.phone !== undefined ||
269
+ clientProfile.email !== undefined ||
270
+ clientProfile.accountType !== undefined ||
271
+ clientProfile.loanApplicationStatus !== undefined) && (
272
+ <>
273
+ <Separator />
274
+ <div className="flex flex-col gap-3 px-6 py-5">
275
+ <p className="text-sm font-semibold">Client Profile</p>
276
+ <div className="flex flex-col gap-2">
277
+ {clientProfile.phone && (
278
+ <div className="flex items-center gap-3 text-sm">
279
+ <Phone className="h-4 w-4 shrink-0 text-muted-foreground" />
280
+ <span className="text-muted-foreground">
281
+ {clientProfile.phone}
282
+ </span>
283
+ </div>
284
+ )}
285
+ {clientProfile.email && (
286
+ <div className="flex items-center gap-3 text-sm">
287
+ <Mail className="h-4 w-4 shrink-0 text-muted-foreground" />
288
+ <span className="text-muted-foreground">
289
+ {clientProfile.email}
290
+ </span>
291
+ </div>
292
+ )}
293
+ {clientProfile.accountType !== undefined && (
294
+ <div className="flex items-center justify-between text-sm">
295
+ <span className="text-muted-foreground">
296
+ Account type
297
+ </span>
298
+ <div className="flex items-center gap-1.5">
299
+ {clientProfile.accountType === "Joint" ? (
300
+ <Users className="h-3.5 w-3.5 text-muted-foreground" />
301
+ ) : (
302
+ <CircleUser className="h-3.5 w-3.5 text-muted-foreground" />
303
+ )}
304
+ <span className="font-medium">
305
+ {clientProfile.accountType}
306
+ </span>
307
+ </div>
308
+ </div>
309
+ )}
310
+ {clientProfile.loanApplicationStatus !== undefined && (
311
+ <div className="flex items-center justify-between text-sm">
312
+ <span className="text-muted-foreground">
313
+ Loan application
314
+ </span>
315
+ <div className="flex items-center gap-1.5">
316
+ <FileText className="h-3.5 w-3.5 text-muted-foreground" />
317
+ {clientProfile.loanApplicationStatus ===
318
+ "finished" && (
319
+ <Badge variant="success" className="text-[10px]">
320
+ Finished
321
+ </Badge>
322
+ )}
323
+ {clientProfile.loanApplicationStatus ===
324
+ "in-progress" && (
325
+ <Badge variant="warning" className="text-[10px]">
326
+ In Progress
327
+ </Badge>
328
+ )}
329
+ {clientProfile.loanApplicationStatus ===
330
+ "sent-request" && (
331
+ <Badge variant="info" className="text-[10px]">
332
+ Sent Request
333
+ </Badge>
334
+ )}
335
+ {clientProfile.loanApplicationStatus ===
336
+ "not-start" && (
337
+ <Badge variant="secondary" className="text-[10px]">
338
+ Not Started
339
+ </Badge>
340
+ )}
341
+ </div>
342
+ </div>
343
+ )}
344
+ </div>
345
+ </div>
346
+ </>
347
+ )}
348
+
349
+ {/* ── Notes ── */}
350
+ {appointment.notes && (
351
+ <>
352
+ <Separator />
353
+ <div className="flex flex-col gap-2 px-6 py-5">
354
+ <div className="flex items-center gap-2">
355
+ <FileText className="h-4 w-4 text-muted-foreground" />
356
+ <p className="text-sm font-semibold">Notes</p>
357
+ </div>
358
+ <p className="text-sm text-muted-foreground">
359
+ {appointment.notes}
360
+ </p>
361
+ </div>
362
+ </>
363
+ )}
364
+
365
+ {/* ── Cancellation Reason ── */}
366
+ {appointment.cancelReason && (
367
+ <>
368
+ <Separator />
369
+ <div className="flex flex-col gap-2 px-6 py-5">
370
+ <div className="flex items-center gap-2">
371
+ <AlertCircle className="h-4 w-4 text-destructive" />
372
+ <p className="text-sm font-semibold">Cancellation Reason</p>
373
+ </div>
374
+ <p className="text-sm text-muted-foreground">
375
+ {appointment.cancelReason}
376
+ </p>
377
+ </div>
378
+ </>
379
+ )}
380
+ </SheetContent>
381
+ </Sheet>
382
+
383
+ {/* Dialogs rendered outside Sheet to avoid z-index issues */}
384
+ <AppointmentConfirmDialog
385
+ open={confirmAction !== null}
386
+ onOpenChange={(v) => !v && setConfirmAction(null)}
387
+ action={confirmAction ?? "accept"}
388
+ clientName={appointment.clientName}
389
+ onConfirm={() => {
390
+ if (confirmAction === "accept") {
391
+ onAccept?.(appointment.id);
392
+ } else {
393
+ onDecline?.(appointment.id);
394
+ }
395
+ setConfirmAction(null);
396
+ onOpenChange(false);
397
+ }}
398
+ />
399
+
400
+ <AppointmentRescheduleDialog
401
+ open={rescheduleOpen}
402
+ onOpenChange={setRescheduleOpen}
403
+ amSlots={amSlots}
404
+ pmSlots={pmSlots}
405
+ currentDate={appointment.date}
406
+ currentTimeStart={appointment.timeStart}
407
+ currentTimeEnd={appointment.timeEnd}
408
+ onReschedule={(date, slot, note) => {
409
+ onReschedule?.(appointment.id, date, slot, note);
410
+ setRescheduleOpen(false);
411
+ }}
412
+ />
413
+ </>
414
+ );
415
+ }
@@ -0,0 +1,188 @@
1
+ import { Badge } from "./badge";
2
+ import { Button } from "./button";
3
+ import { Input } from "./input";
4
+ import { Separator } from "./separator";
5
+ import { Spinner } from "./spinner";
6
+ import { Check, Copy, Link2, Mail, ShieldCheck, Unlink } from "lucide-react";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Types
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export type GmailConnectionState = "disconnected" | "connecting" | "connected";
13
+
14
+ export interface GmailConnection {
15
+ connected: boolean;
16
+ email?: string;
17
+ /** ISO date string — "2026-03-15" */
18
+ connectedAt?: string;
19
+ }
20
+
21
+ export interface AppointmentGmailConnectProps {
22
+ connection: GmailConnection;
23
+ state?: GmailConnectionState;
24
+ onConnect?: () => void;
25
+ onDisconnect?: () => void;
26
+ /** Booking link to display in the calendar link section (connected state only) */
27
+ calendarLink?: string;
28
+ /** Called when the user clicks the copy button next to the calendar link */
29
+ onCopyLink?: () => void;
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Constants
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const PERMISSIONS = [
37
+ "Read your calendar availability",
38
+ "Send appointment confirmation emails",
39
+ "Create calendar events on your behalf",
40
+ ];
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Component
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export function AppointmentGmailConnect({
47
+ connection,
48
+ state = "disconnected",
49
+ onConnect,
50
+ onDisconnect,
51
+ calendarLink,
52
+ onCopyLink,
53
+ }: AppointmentGmailConnectProps) {
54
+ if (state === "connecting") {
55
+ return (
56
+ <div className="flex flex-col items-center gap-4 border border-border bg-card px-6 py-8 text-center">
57
+ <Spinner className="h-6 w-6 text-primary" />
58
+ <div className="flex flex-col gap-1">
59
+ <p className="text-sm font-medium">Connecting to Gmail…</p>
60
+ <p className="text-xs text-muted-foreground">
61
+ Complete the sign-in in the popup window.
62
+ </p>
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+
68
+ if (connection.connected) {
69
+ return (
70
+ <div className="flex flex-col border border-border bg-card">
71
+ {/* Header — mail icon + title/email + badge + connected-since + disconnect */}
72
+ <div className="flex items-center gap-3 px-5 py-4">
73
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center bg-primary/10">
74
+ <Mail className="h-5 w-5 text-primary" />
75
+ </div>
76
+ <div className="flex-1 min-w-0">
77
+ <p className="text-base font-semibold">Gmail Connected</p>
78
+ <p className="truncate text-sm text-muted-foreground">
79
+ {connection.email}
80
+ </p>
81
+ </div>
82
+ <Badge variant="success">
83
+ <Check size={10} />
84
+ Active
85
+ </Badge>
86
+ {connection.connectedAt && (
87
+ <p className="shrink-0 text-sm text-muted-foreground">
88
+ Connected since {connection.connectedAt}
89
+ </p>
90
+ )}
91
+ <Button
92
+ size="sm"
93
+ variant="ghost"
94
+ className="shrink-0 gap-1.5 text-destructive hover:text-destructive"
95
+ onClick={onDisconnect}
96
+ >
97
+ <Unlink className="h-3.5 w-3.5" />
98
+ Disconnect
99
+ </Button>
100
+ </div>
101
+
102
+ <Separator />
103
+
104
+ {/* Permissions granted */}
105
+ <div className="flex flex-col gap-2 px-5 py-4">
106
+ <p className="text-sm font-medium text-muted-foreground">
107
+ Permissions granted
108
+ </p>
109
+ {PERMISSIONS.map((perm) => (
110
+ <div key={perm} className="flex items-center gap-2">
111
+ <ShieldCheck className="h-4 w-4 shrink-0 text-success" />
112
+ <span className="text-sm text-muted-foreground">{perm}</span>
113
+ </div>
114
+ ))}
115
+ </div>
116
+
117
+ {/* Calendar appointment link — only rendered when a link is available */}
118
+ {calendarLink && (
119
+ <>
120
+ <Separator />
121
+ <div className="flex flex-col gap-2.5 px-5 py-4">
122
+ <div className="flex items-center gap-2">
123
+ <Link2 className="h-4 w-4 text-muted-foreground" />
124
+ <p className="text-sm font-semibold">
125
+ Calendar Appointment Link
126
+ </p>
127
+ </div>
128
+ <p className="text-sm text-muted-foreground">
129
+ Share with clients so they can self-book.
130
+ </p>
131
+ <div className="relative">
132
+ <Input
133
+ id="calendar-link"
134
+ readOnly
135
+ value={calendarLink}
136
+ className="cursor-default select-all pr-10 text-muted-foreground"
137
+ />
138
+ <Button
139
+ type="button"
140
+ variant="ghost"
141
+ size="icon"
142
+ onClick={onCopyLink}
143
+ className="absolute right-1 top-1/2 h-8 w-8 -translate-y-1/2"
144
+ >
145
+ <Copy className="h-4 w-4" />
146
+ </Button>
147
+ </div>
148
+ </div>
149
+ </>
150
+ )}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ // Disconnected state
156
+ return (
157
+ <div className="flex flex-col items-center gap-4 border border-border bg-card px-6 py-8 text-center">
158
+ <div className="flex h-12 w-12 items-center justify-center bg-muted">
159
+ <Mail className="h-6 w-6 text-muted-foreground" />
160
+ </div>
161
+
162
+ <div className="flex flex-col gap-1.5">
163
+ <p className="text-base font-semibold">Connect your Gmail account</p>
164
+ <p className="max-w-xs text-sm text-muted-foreground">
165
+ Link your Gmail to let clients book appointments and receive
166
+ confirmation emails automatically.
167
+ </p>
168
+ </div>
169
+
170
+ <div className="flex w-full max-w-xs flex-col gap-2 text-left">
171
+ <p className="text-sm font-medium text-muted-foreground">
172
+ This will allow WealthX to:
173
+ </p>
174
+ {PERMISSIONS.map((perm) => (
175
+ <div key={perm} className="flex items-center gap-2">
176
+ <Check className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
177
+ <span className="text-sm text-muted-foreground">{perm}</span>
178
+ </div>
179
+ ))}
180
+ </div>
181
+
182
+ <Button onClick={onConnect} className="gap-2">
183
+ <Mail className="h-4 w-4" />
184
+ Connect Gmail
185
+ </Button>
186
+ </div>
187
+ );
188
+ }