@wealthx/shadcn 1.5.10 → 1.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +114 -114
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-AANINK2B.mjs → chunk-2KNQZG5S.mjs} +1 -1
- package/dist/chunk-3KLJ4XRE.mjs +375 -0
- package/dist/{chunk-6U4NQGVM.mjs → chunk-4X4MGYHE.mjs} +2 -2
- package/dist/{chunk-CEEVYRQA.mjs → chunk-67DGIPQ4.mjs} +1 -1
- package/dist/{chunk-7UIL5UN3.mjs → chunk-7II6QRCZ.mjs} +1 -1
- package/dist/{chunk-W5QJ57PU.mjs → chunk-7LN5OGC2.mjs} +1 -1
- package/dist/{chunk-ZXEUBBHJ.mjs → chunk-7TMPOZDE.mjs} +1 -1
- package/dist/{chunk-AHSCWXYJ.mjs → chunk-AJUAJC5O.mjs} +1 -1
- package/dist/{chunk-3CGM3QXQ.mjs → chunk-AKWN5ZQG.mjs} +2 -2
- package/dist/{chunk-O5CP6VP6.mjs → chunk-CPM6P63C.mjs} +56 -44
- package/dist/{chunk-FRT3S72S.mjs → chunk-CQ7HKBEX.mjs} +1 -1
- package/dist/{chunk-54TRNCID.mjs → chunk-EB626HVW.mjs} +78 -11
- package/dist/{chunk-E2BNCA6L.mjs → chunk-EHQL64B7.mjs} +1 -1
- package/dist/{chunk-2WCIORP7.mjs → chunk-EXI64H46.mjs} +1 -1
- package/dist/{chunk-BBXSNDS3.mjs → chunk-FQYFPHDO.mjs} +1 -1
- package/dist/{chunk-3VZ6CYY2.mjs → chunk-GAXNO4JB.mjs} +1 -1
- package/dist/{chunk-3WGFIFP6.mjs → chunk-I4P7RXAE.mjs} +1 -1
- package/dist/{chunk-Z2BW5T7P.mjs → chunk-IODGRCQG.mjs} +1 -1
- package/dist/{chunk-GS47ZSSA.mjs → chunk-J7KQON2N.mjs} +20 -5
- package/dist/{chunk-IQGKOT7A.mjs → chunk-K35TFQUB.mjs} +1 -1
- package/dist/{chunk-4DO3WM7V.mjs → chunk-K4VWSDJJ.mjs} +1 -1
- package/dist/{chunk-KWD6GANL.mjs → chunk-MPA2HV5U.mjs} +1 -1
- package/dist/{chunk-5LZZYODG.mjs → chunk-QHAMVWDG.mjs} +19 -1
- package/dist/{chunk-XUCDPAVI.mjs → chunk-R6U246E4.mjs} +2 -2
- package/dist/{chunk-VCDGLN25.mjs → chunk-S6AYZJYO.mjs} +47 -21
- package/dist/{chunk-WL6WVV47.mjs → chunk-X6RC5UWB.mjs} +1 -1
- package/dist/{chunk-4BHDDLWK.mjs → chunk-XAS6KBIG.mjs} +2 -2
- package/dist/{chunk-VWZS32ZQ.mjs → chunk-XYWEGBAA.mjs} +1 -1
- package/dist/{chunk-54MTIKNC.mjs → chunk-YV7XF32X.mjs} +49 -24
- package/dist/{chunk-E5EDZQ5J.mjs → chunk-ZA44WICP.mjs} +1 -1
- package/dist/components/ui/advisor-card.js +144 -55
- package/dist/components/ui/advisor-card.mjs +5 -2
- package/dist/components/ui/agent-evaluation-toast.js +1 -1
- package/dist/components/ui/agent-evaluation-toast.mjs +2 -2
- package/dist/components/ui/ai-assistant-drawer.js +1 -1
- package/dist/components/ui/ai-assistant-drawer.mjs +2 -2
- package/dist/components/ui/ai-builder.js +1 -1
- package/dist/components/ui/ai-builder.mjs +2 -2
- package/dist/components/ui/ai-conversations.js +71 -4
- package/dist/components/ui/ai-conversations.mjs +3 -3
- package/dist/components/ui/appointment-action-dialogs.js +1 -1
- package/dist/components/ui/appointment-action-dialogs.mjs +3 -3
- package/dist/components/ui/appointment-book-dialog.js +19 -4
- package/dist/components/ui/appointment-book-dialog.mjs +3 -3
- package/dist/components/ui/appointment-calendar-view.js +1 -1
- package/dist/components/ui/appointment-calendar-view.mjs +2 -2
- package/dist/components/ui/appointment-detail-sheet.js +1 -1
- package/dist/components/ui/appointment-detail-sheet.mjs +4 -4
- package/dist/components/ui/appointment-gmail-connect.js +1 -1
- package/dist/components/ui/appointment-gmail-connect.mjs +2 -2
- package/dist/components/ui/appointment-time-slot-picker.js +1 -1
- package/dist/components/ui/appointment-time-slot-picker.mjs +2 -2
- package/dist/components/ui/appointment-upcoming-card.js +1 -1
- package/dist/components/ui/appointment-upcoming-card.mjs +3 -3
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/badge.mjs +1 -1
- package/dist/components/ui/bank-statement-generate-dialog.js +61 -46
- package/dist/components/ui/bank-statement-generate-dialog.mjs +1 -1
- package/dist/components/ui/chat-widget-primitives.js +1 -1
- package/dist/components/ui/chat-widget-primitives.mjs +2 -2
- package/dist/components/ui/chat-widget.js +1 -1
- package/dist/components/ui/chat-widget.mjs +3 -3
- package/dist/components/ui/chip.js +1 -1
- package/dist/components/ui/chip.mjs +2 -2
- package/dist/components/ui/contact-alert-dialog/index.js +19 -1
- package/dist/components/ui/contact-alert-dialog/index.mjs +1 -1
- package/dist/components/ui/dashboard-transactions-table.js +1 -1
- package/dist/components/ui/dashboard-transactions-table.mjs +2 -2
- package/dist/components/ui/financial-cards.js +1 -1
- package/dist/components/ui/financial-cards.mjs +2 -2
- package/dist/components/ui/financial-sections.js +1 -1
- package/dist/components/ui/financial-sections.mjs +3 -3
- package/dist/components/ui/income-summary-component.js +1 -1
- package/dist/components/ui/income-summary-component.mjs +1 -1
- package/dist/components/ui/integration-card.js +1 -1
- package/dist/components/ui/integration-card.mjs +2 -2
- package/dist/components/ui/kanban-column.js +46 -23
- package/dist/components/ui/kanban-column.mjs +4 -4
- package/dist/components/ui/loan-applicant-information.js +1 -1
- package/dist/components/ui/loan-applicant-information.mjs +1 -1
- package/dist/components/ui/loan-application-badge.js +1 -1
- package/dist/components/ui/loan-application-badge.mjs +2 -2
- package/dist/components/ui/opportunity-card.js +46 -23
- package/dist/components/ui/opportunity-card.mjs +3 -3
- package/dist/components/ui/opportunity-summary-tab.js +1 -1
- package/dist/components/ui/opportunity-summary-tab.mjs +3 -3
- package/dist/components/ui/pipeline-board.js +46 -23
- package/dist/components/ui/pipeline-board.mjs +5 -5
- package/dist/components/ui/pipeline-primitives.js +1 -1
- package/dist/components/ui/pipeline-primitives.mjs +2 -2
- package/dist/components/ui/property-asset-card.js +1 -1
- package/dist/components/ui/property-asset-card.mjs +1 -1
- package/dist/components/ui/resource-center.js +1 -1
- package/dist/components/ui/resource-center.mjs +2 -2
- package/dist/components/ui/share-details-dialog.js +326 -30
- package/dist/components/ui/share-details-dialog.mjs +4 -1
- package/dist/components/ui/stage-timeline.js +1 -1
- package/dist/components/ui/stage-timeline.mjs +3 -3
- package/dist/index.js +583 -232
- package/dist/index.mjs +45 -43
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/index.tsx +4 -0
- package/src/components/ui/advisor-card.tsx +75 -25
- package/src/components/ui/ai-conversations.tsx +157 -23
- package/src/components/ui/appointment-book-dialog.tsx +26 -3
- package/src/components/ui/appointment-time-slot-picker.tsx +1 -0
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/bank-statement-generate-dialog.tsx +84 -61
- package/src/components/ui/contact-alert-dialog/contact-alert-dialog.tsx +19 -1
- package/src/components/ui/opportunity-card.tsx +56 -20
- package/src/components/ui/share-details-dialog.tsx +251 -0
- package/src/styles/styles-css.ts +1 -1
- package/dist/chunk-OZ2R6ERP.mjs +0 -174
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { format, parseISO, subDays } from "date-fns";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
3
|
import { Button } from "@/components/ui/button";
|
|
5
4
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
6
5
|
import { DatePicker } from "@/components/ui/date-picker";
|
|
@@ -111,6 +110,15 @@ export interface BankStatementGenerateDialogProps {
|
|
|
111
110
|
className?: string;
|
|
112
111
|
}
|
|
113
112
|
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Constants
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
const APPLICANT_TYPE_LABELS: Record<"primary" | "secondary", string> = {
|
|
118
|
+
primary: "Main applicant",
|
|
119
|
+
secondary: "Co-applicant",
|
|
120
|
+
};
|
|
121
|
+
|
|
114
122
|
// ---------------------------------------------------------------------------
|
|
115
123
|
// Helpers
|
|
116
124
|
// ---------------------------------------------------------------------------
|
|
@@ -144,8 +152,11 @@ export function BankStatementGenerateDialog({
|
|
|
144
152
|
}: BankStatementGenerateDialogProps) {
|
|
145
153
|
const [statementName, setStatementName] = useState("Bank Statement 1");
|
|
146
154
|
const [rangePreset, setRangePreset] = useState<BankStatementRangePreset>(90);
|
|
147
|
-
|
|
148
|
-
const [
|
|
155
|
+
// Initialise immediately so the Period column never shows "—" on first open.
|
|
156
|
+
const [fromDate, setFromDate] = useState<string>(
|
|
157
|
+
() => presetToDateRange(90).from,
|
|
158
|
+
);
|
|
159
|
+
const [toDate, setToDate] = useState<string>(() => presetToDateRange(90).to);
|
|
149
160
|
const [applicantType, setApplicantType] = useState<
|
|
150
161
|
"primary" | "secondary" | ""
|
|
151
162
|
>("");
|
|
@@ -185,6 +196,12 @@ export function BankStatementGenerateDialog({
|
|
|
185
196
|
}
|
|
186
197
|
};
|
|
187
198
|
|
|
199
|
+
const handlePresetChange = (val: string[]) => {
|
|
200
|
+
if (val.length === 0) return;
|
|
201
|
+
const raw = val[0];
|
|
202
|
+
applyPreset(raw === "custom" ? "custom" : (Number(raw) as 90 | 180 | 365));
|
|
203
|
+
};
|
|
204
|
+
|
|
188
205
|
// ── Account selection ────────────────────────────────────────────────────
|
|
189
206
|
|
|
190
207
|
const areAllSelected =
|
|
@@ -240,7 +257,8 @@ export function BankStatementGenerateDialog({
|
|
|
240
257
|
return (
|
|
241
258
|
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
|
242
259
|
<DialogContent
|
|
243
|
-
|
|
260
|
+
size="3xl"
|
|
261
|
+
className={className}
|
|
244
262
|
data-slot="bank-statement-generate-dialog"
|
|
245
263
|
>
|
|
246
264
|
<DialogHeader>
|
|
@@ -267,15 +285,7 @@ export function BankStatementGenerateDialog({
|
|
|
267
285
|
type="single"
|
|
268
286
|
variant="outline"
|
|
269
287
|
value={[String(rangePreset)]}
|
|
270
|
-
onValueChange={
|
|
271
|
-
if (val.length === 0) return;
|
|
272
|
-
const raw = val[0];
|
|
273
|
-
if (raw === "custom") {
|
|
274
|
-
applyPreset("custom");
|
|
275
|
-
} else {
|
|
276
|
-
applyPreset(Number(raw) as 90 | 180 | 365);
|
|
277
|
-
}
|
|
278
|
-
}}
|
|
288
|
+
onValueChange={handlePresetChange}
|
|
279
289
|
className="w-full"
|
|
280
290
|
>
|
|
281
291
|
<ToggleGroupItem value="90" className="flex-1">
|
|
@@ -317,14 +327,19 @@ export function BankStatementGenerateDialog({
|
|
|
317
327
|
onValueChange={(v) =>
|
|
318
328
|
handleApplicantTypeChange(v as "primary" | "secondary")
|
|
319
329
|
}
|
|
330
|
+
items={APPLICANT_TYPE_LABELS}
|
|
320
331
|
>
|
|
321
332
|
<SelectTrigger className="w-full">
|
|
322
333
|
<SelectValue placeholder="Applicant Type" />
|
|
323
334
|
</SelectTrigger>
|
|
324
335
|
<SelectContent>
|
|
325
|
-
<SelectItem value="primary">
|
|
336
|
+
<SelectItem value="primary">
|
|
337
|
+
{APPLICANT_TYPE_LABELS.primary}
|
|
338
|
+
</SelectItem>
|
|
326
339
|
{hasCoApplicant && (
|
|
327
|
-
<SelectItem value="secondary">
|
|
340
|
+
<SelectItem value="secondary">
|
|
341
|
+
{APPLICANT_TYPE_LABELS.secondary}
|
|
342
|
+
</SelectItem>
|
|
328
343
|
)}
|
|
329
344
|
</SelectContent>
|
|
330
345
|
</Select>
|
|
@@ -363,53 +378,61 @@ export function BankStatementGenerateDialog({
|
|
|
363
378
|
</TableRow>
|
|
364
379
|
</TableHeader>
|
|
365
380
|
<TableBody>
|
|
366
|
-
{bankAccounts.map((account) =>
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
381
|
+
{bankAccounts.map((account) => {
|
|
382
|
+
const isSelected = selectedAccountIds.includes(
|
|
383
|
+
account.id,
|
|
384
|
+
);
|
|
385
|
+
return (
|
|
386
|
+
<TableRow
|
|
387
|
+
key={account.id}
|
|
388
|
+
data-state={isSelected ? "selected" : undefined}
|
|
389
|
+
>
|
|
390
|
+
<TableCell>
|
|
391
|
+
<Checkbox
|
|
392
|
+
checked={isSelected}
|
|
393
|
+
onCheckedChange={() =>
|
|
394
|
+
handleToggleAccount(account.id)
|
|
395
|
+
}
|
|
396
|
+
aria-label={`Select ${account.name}`}
|
|
397
|
+
/>
|
|
398
|
+
</TableCell>
|
|
399
|
+
<TableCell>
|
|
400
|
+
<div className="flex items-center gap-2">
|
|
401
|
+
{account.institutionLogo && (
|
|
402
|
+
<img
|
|
403
|
+
src={account.institutionLogo}
|
|
404
|
+
alt={account.institutionName ?? ""}
|
|
405
|
+
className="size-8 object-cover"
|
|
406
|
+
/>
|
|
407
|
+
)}
|
|
408
|
+
<span className="text-body-medium font-semibold">
|
|
409
|
+
{account.name || account.institutionName || "—"}
|
|
410
|
+
</span>
|
|
411
|
+
</div>
|
|
412
|
+
</TableCell>
|
|
413
|
+
<TableCell>
|
|
414
|
+
<span className="text-body-medium">
|
|
415
|
+
{account.accountNo ?? "—"}
|
|
416
|
+
</span>
|
|
417
|
+
</TableCell>
|
|
418
|
+
<TableCell>
|
|
419
|
+
<span className="text-body-medium">
|
|
420
|
+
{periodLabel}
|
|
421
|
+
</span>
|
|
422
|
+
</TableCell>
|
|
423
|
+
<TableCell>
|
|
424
|
+
<span className="text-body-medium">
|
|
425
|
+
{account.lastUpdated
|
|
426
|
+
? format(
|
|
427
|
+
parseISO(account.lastUpdated),
|
|
428
|
+
"dd MMM yyyy",
|
|
429
|
+
)
|
|
430
|
+
: "—"}
|
|
388
431
|
</span>
|
|
389
|
-
</
|
|
390
|
-
</
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
{account.accountNo ?? "—"}
|
|
394
|
-
</span>
|
|
395
|
-
</TableCell>
|
|
396
|
-
<TableCell>
|
|
397
|
-
<span className="text-body-medium">
|
|
398
|
-
{periodLabel}
|
|
399
|
-
</span>
|
|
400
|
-
</TableCell>
|
|
401
|
-
<TableCell>
|
|
402
|
-
<span className="text-body-medium">
|
|
403
|
-
{account.lastUpdated
|
|
404
|
-
? format(
|
|
405
|
-
parseISO(account.lastUpdated),
|
|
406
|
-
"dd MMM yyyy",
|
|
407
|
-
)
|
|
408
|
-
: "—"}
|
|
409
|
-
</span>
|
|
410
|
-
</TableCell>
|
|
411
|
-
</TableRow>
|
|
412
|
-
))}
|
|
432
|
+
</TableCell>
|
|
433
|
+
</TableRow>
|
|
434
|
+
);
|
|
435
|
+
})}
|
|
413
436
|
</TableBody>
|
|
414
437
|
</Table>
|
|
415
438
|
)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { AlertCircleIcon, EyeIcon, InfoIcon } from "lucide-react";
|
|
2
3
|
import {
|
|
3
4
|
Query,
|
|
4
5
|
Utils as QbUtils,
|
|
@@ -17,6 +18,7 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|
|
17
18
|
import { Label } from "@/components/ui/label";
|
|
18
19
|
import { Field, FieldLabel } from "@/components/ui/field";
|
|
19
20
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
|
21
|
+
import { cn } from "@/lib/utils";
|
|
20
22
|
import type {
|
|
21
23
|
ContactAlertDialogProps,
|
|
22
24
|
ContactAlertQueryBuilderProps,
|
|
@@ -161,7 +163,23 @@ export function ContactAlertDialog({
|
|
|
161
163
|
{(
|
|
162
164
|
["NEED_ACTION", "WATCH", "HEALTHY"] as ContactAlertSeverity[]
|
|
163
165
|
).map((s) => (
|
|
164
|
-
<ToggleGroupItem
|
|
166
|
+
<ToggleGroupItem
|
|
167
|
+
key={s}
|
|
168
|
+
value={s}
|
|
169
|
+
className={cn(
|
|
170
|
+
s === "NEED_ACTION" &&
|
|
171
|
+
"data-pressed:bg-destructive/10 data-pressed:inset-ring-destructive data-pressed:text-destructive-text data-pressed:hover:bg-destructive/10",
|
|
172
|
+
s === "WATCH" &&
|
|
173
|
+
"data-pressed:bg-warning/10 data-pressed:inset-ring-warning data-pressed:text-warning-text data-pressed:hover:bg-warning/10",
|
|
174
|
+
s === "HEALTHY" &&
|
|
175
|
+
"data-pressed:bg-info/10 data-pressed:inset-ring-info data-pressed:text-info-text data-pressed:hover:bg-info/10",
|
|
176
|
+
)}
|
|
177
|
+
>
|
|
178
|
+
{s === "NEED_ACTION" && (
|
|
179
|
+
<AlertCircleIcon className="size-3.5" />
|
|
180
|
+
)}
|
|
181
|
+
{s === "WATCH" && <EyeIcon className="size-3.5" />}
|
|
182
|
+
{s === "HEALTHY" && <InfoIcon className="size-3.5" />}
|
|
165
183
|
{SEVERITY_LABELS[s]}
|
|
166
184
|
</ToggleGroupItem>
|
|
167
185
|
))}
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
Users,
|
|
7
7
|
Calendar,
|
|
8
8
|
Check,
|
|
9
|
+
Copy,
|
|
10
|
+
Link2,
|
|
9
11
|
MoreVertical,
|
|
10
12
|
Clock,
|
|
11
13
|
ArrowRight,
|
|
@@ -782,6 +784,54 @@ export function OpportunityCard({
|
|
|
782
784
|
// Primary action: send them the loan application link.
|
|
783
785
|
// ---------------------------------------------------------------------------
|
|
784
786
|
|
|
787
|
+
function normalizeUrl(url: string): string {
|
|
788
|
+
return `https://${url.replace(/^https?:\/\//, "")}`;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/** Shortened + copyable loan application URL row. */
|
|
792
|
+
function LoanApplicationLink({ url }: { url: string }) {
|
|
793
|
+
const [copied, setCopied] = useState(false);
|
|
794
|
+
const href = normalizeUrl(url);
|
|
795
|
+
|
|
796
|
+
function handleCopy() {
|
|
797
|
+
navigator.clipboard.writeText(href).then(() => {
|
|
798
|
+
setCopied(true);
|
|
799
|
+
setTimeout(() => setCopied(false), 2000);
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return (
|
|
804
|
+
<div className="flex flex-col gap-1">
|
|
805
|
+
<p className="text-caption text-muted-foreground">
|
|
806
|
+
Or the link below to fill out the loan application directly.
|
|
807
|
+
</p>
|
|
808
|
+
<div className="flex items-center gap-1.5">
|
|
809
|
+
<Link2 className="size-4 shrink-0 text-muted-foreground" />
|
|
810
|
+
<a
|
|
811
|
+
href={href}
|
|
812
|
+
target="_blank"
|
|
813
|
+
rel="noreferrer"
|
|
814
|
+
className="min-w-0 flex-1 truncate text-body-small text-primary underline-offset-2 hover:underline"
|
|
815
|
+
>
|
|
816
|
+
{url}
|
|
817
|
+
</a>
|
|
818
|
+
<button
|
|
819
|
+
type="button"
|
|
820
|
+
onClick={handleCopy}
|
|
821
|
+
className="shrink-0 rounded p-1 text-muted-foreground transition-colors hover:text-foreground"
|
|
822
|
+
aria-label="Copy link"
|
|
823
|
+
>
|
|
824
|
+
{copied ? (
|
|
825
|
+
<Check className="size-4 text-success-text" />
|
|
826
|
+
) : (
|
|
827
|
+
<Copy className="size-4" />
|
|
828
|
+
)}
|
|
829
|
+
</button>
|
|
830
|
+
</div>
|
|
831
|
+
</div>
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
|
|
785
835
|
export interface LeadCardProps {
|
|
786
836
|
id: string;
|
|
787
837
|
customerName: string;
|
|
@@ -816,18 +866,18 @@ export function LeadCard({
|
|
|
816
866
|
{/* ── Customer info + delete menu ── */}
|
|
817
867
|
<div className="flex items-start justify-between gap-2">
|
|
818
868
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
819
|
-
<span className="text-
|
|
869
|
+
<span className="text-label-medium text-foreground">
|
|
820
870
|
{customerName}
|
|
821
871
|
</span>
|
|
822
872
|
{customerPhone && (
|
|
823
|
-
<span className="flex items-center gap-1.5 text-
|
|
824
|
-
<Phone className="size-3 shrink-0" aria-hidden="true" />
|
|
873
|
+
<span className="flex items-center gap-1.5 text-caption text-muted-foreground">
|
|
874
|
+
<Phone className="size-3.5 shrink-0" aria-hidden="true" />
|
|
825
875
|
{customerPhone}
|
|
826
876
|
</span>
|
|
827
877
|
)}
|
|
828
878
|
{customerEmail && (
|
|
829
|
-
<span className="flex items-center gap-1.5 text-
|
|
830
|
-
<Mail className="size-3 shrink-0" aria-hidden="true" />
|
|
879
|
+
<span className="flex items-center gap-1.5 text-caption text-muted-foreground">
|
|
880
|
+
<Mail className="size-3.5 shrink-0" aria-hidden="true" />
|
|
831
881
|
<span className="truncate">{customerEmail}</span>
|
|
832
882
|
</span>
|
|
833
883
|
)}
|
|
@@ -871,21 +921,7 @@ export function LeadCard({
|
|
|
871
921
|
Send Loan Application Request
|
|
872
922
|
</Button>
|
|
873
923
|
{loanApplicationUrl && (
|
|
874
|
-
<
|
|
875
|
-
Or the link below to fill out the loan application directly.
|
|
876
|
-
<br />
|
|
877
|
-
<a
|
|
878
|
-
href={`https://${loanApplicationUrl.replace(
|
|
879
|
-
/^https?:\/\//,
|
|
880
|
-
"",
|
|
881
|
-
)}`}
|
|
882
|
-
target="_blank"
|
|
883
|
-
rel="noreferrer"
|
|
884
|
-
className="text-primary underline-offset-2 hover:underline"
|
|
885
|
-
>
|
|
886
|
-
{loanApplicationUrl}
|
|
887
|
-
</a>
|
|
888
|
-
</p>
|
|
924
|
+
<LoanApplicationLink url={loanApplicationUrl} />
|
|
889
925
|
)}
|
|
890
926
|
</div>
|
|
891
927
|
)}
|
|
@@ -12,6 +12,8 @@ import { Input } from "@/components/ui/input";
|
|
|
12
12
|
import { Label } from "@/components/ui/label";
|
|
13
13
|
import { Spinner } from "@/components/ui/spinner";
|
|
14
14
|
import { Textarea } from "@/components/ui/textarea";
|
|
15
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
15
17
|
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
17
19
|
// Shared helper
|
|
@@ -236,3 +238,252 @@ export function EmailTemplateDialog({
|
|
|
236
238
|
</Dialog>
|
|
237
239
|
);
|
|
238
240
|
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// ShareContactDialog
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
export interface InternalAdvisor {
|
|
247
|
+
id: string;
|
|
248
|
+
name: string;
|
|
249
|
+
role: string;
|
|
250
|
+
practice: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface ShareContactDialogProps {
|
|
254
|
+
open: boolean;
|
|
255
|
+
onOpenChange: (open: boolean) => void;
|
|
256
|
+
contactName: string;
|
|
257
|
+
internalAdvisors: InternalAdvisor[];
|
|
258
|
+
onShareInternal: (advisorId: string, notes: string) => void;
|
|
259
|
+
onShareExternal: (payload: {
|
|
260
|
+
firstName: string;
|
|
261
|
+
lastName: string;
|
|
262
|
+
email: string;
|
|
263
|
+
notes: string;
|
|
264
|
+
}) => void;
|
|
265
|
+
isSending?: boolean;
|
|
266
|
+
className?: string;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function ShareContactDialog({
|
|
270
|
+
open,
|
|
271
|
+
onOpenChange,
|
|
272
|
+
contactName,
|
|
273
|
+
internalAdvisors,
|
|
274
|
+
onShareInternal,
|
|
275
|
+
onShareExternal,
|
|
276
|
+
isSending = false,
|
|
277
|
+
className,
|
|
278
|
+
}: ShareContactDialogProps) {
|
|
279
|
+
const [tab, setTab] = React.useState("internal");
|
|
280
|
+
const [search, setSearch] = React.useState("");
|
|
281
|
+
const [selectedId, setSelectedId] = React.useState<string | null>(null);
|
|
282
|
+
const [internalNotes, setInternalNotes] = React.useState("");
|
|
283
|
+
const [firstName, setFirstName] = React.useState("");
|
|
284
|
+
const [lastName, setLastName] = React.useState("");
|
|
285
|
+
const [email, setEmail] = React.useState("");
|
|
286
|
+
const [externalNotes, setExternalNotes] = React.useState("");
|
|
287
|
+
|
|
288
|
+
function reset() {
|
|
289
|
+
setTab("internal");
|
|
290
|
+
setSearch("");
|
|
291
|
+
setSelectedId(null);
|
|
292
|
+
setInternalNotes("");
|
|
293
|
+
setFirstName("");
|
|
294
|
+
setLastName("");
|
|
295
|
+
setEmail("");
|
|
296
|
+
setExternalNotes("");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function handleOpenChange(v: boolean) {
|
|
300
|
+
if (!v) reset();
|
|
301
|
+
onOpenChange(v);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const filtered = internalAdvisors.filter((a) => {
|
|
305
|
+
const q = search.toLowerCase();
|
|
306
|
+
return a.name.toLowerCase().includes(q) || a.role.toLowerCase().includes(q);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
return (
|
|
310
|
+
<Dialog open={open} onOpenChange={isSending ? undefined : handleOpenChange}>
|
|
311
|
+
<DialogContent size="md" className={className}>
|
|
312
|
+
<DialogHeader>
|
|
313
|
+
<DialogTitle>Share Contact's Information</DialogTitle>
|
|
314
|
+
</DialogHeader>
|
|
315
|
+
|
|
316
|
+
<p className="text-sm text-muted-foreground">
|
|
317
|
+
Share{" "}
|
|
318
|
+
<span className="font-medium text-foreground">{contactName}</span>
|
|
319
|
+
's information with an advisor.
|
|
320
|
+
</p>
|
|
321
|
+
|
|
322
|
+
<Tabs value={tab} onValueChange={setTab}>
|
|
323
|
+
<TabsList className="w-full">
|
|
324
|
+
<TabsTrigger value="internal" className="flex-1">
|
|
325
|
+
Internal Advisor
|
|
326
|
+
</TabsTrigger>
|
|
327
|
+
<TabsTrigger value="external" className="flex-1">
|
|
328
|
+
External / Referral
|
|
329
|
+
</TabsTrigger>
|
|
330
|
+
</TabsList>
|
|
331
|
+
|
|
332
|
+
<TabsContent value="internal" className="mt-4 flex flex-col gap-4">
|
|
333
|
+
<Field label="Search Advisor">
|
|
334
|
+
<Input
|
|
335
|
+
placeholder="Search by name or role…"
|
|
336
|
+
value={search}
|
|
337
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
338
|
+
/>
|
|
339
|
+
</Field>
|
|
340
|
+
|
|
341
|
+
<div className="max-h-[200px] overflow-y-auto border border-border divide-y divide-border">
|
|
342
|
+
{filtered.length === 0 ? (
|
|
343
|
+
<p className="px-3 py-4 text-center text-sm text-muted-foreground">
|
|
344
|
+
No advisors found.
|
|
345
|
+
</p>
|
|
346
|
+
) : (
|
|
347
|
+
filtered.map((a) => {
|
|
348
|
+
const isSelected = selectedId === a.id;
|
|
349
|
+
return (
|
|
350
|
+
<button
|
|
351
|
+
key={a.id}
|
|
352
|
+
type="button"
|
|
353
|
+
onClick={() => setSelectedId(a.id)}
|
|
354
|
+
className={cn(
|
|
355
|
+
"flex w-full items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-muted/40",
|
|
356
|
+
isSelected && "bg-primary/5",
|
|
357
|
+
)}
|
|
358
|
+
>
|
|
359
|
+
<div
|
|
360
|
+
className={cn(
|
|
361
|
+
"flex size-4 shrink-0 items-center justify-center rounded-full border-2",
|
|
362
|
+
isSelected
|
|
363
|
+
? "border-primary"
|
|
364
|
+
: "border-border bg-background",
|
|
365
|
+
)}
|
|
366
|
+
>
|
|
367
|
+
{isSelected && (
|
|
368
|
+
<div className="size-2 rounded-full bg-primary" />
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
<div>
|
|
372
|
+
<p className="text-sm font-medium">{a.name}</p>
|
|
373
|
+
<p className="text-xs text-muted-foreground">
|
|
374
|
+
{a.role} · {a.practice}
|
|
375
|
+
</p>
|
|
376
|
+
</div>
|
|
377
|
+
</button>
|
|
378
|
+
);
|
|
379
|
+
})
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
<Field id="sc-int-notes" label="Notes">
|
|
384
|
+
<Textarea
|
|
385
|
+
id="sc-int-notes"
|
|
386
|
+
placeholder="Optional message for the advisor…"
|
|
387
|
+
rows={3}
|
|
388
|
+
value={internalNotes}
|
|
389
|
+
onChange={(e) => setInternalNotes(e.target.value)}
|
|
390
|
+
disabled={isSending}
|
|
391
|
+
/>
|
|
392
|
+
</Field>
|
|
393
|
+
</TabsContent>
|
|
394
|
+
|
|
395
|
+
<TabsContent value="external" className="mt-4 flex flex-col gap-4">
|
|
396
|
+
<div className="grid grid-cols-2 gap-4">
|
|
397
|
+
<Field id="sc-first" label="First Name">
|
|
398
|
+
<Input
|
|
399
|
+
id="sc-first"
|
|
400
|
+
placeholder="First name"
|
|
401
|
+
value={firstName}
|
|
402
|
+
onChange={(e) => setFirstName(e.target.value)}
|
|
403
|
+
disabled={isSending}
|
|
404
|
+
/>
|
|
405
|
+
</Field>
|
|
406
|
+
<Field id="sc-last" label="Last Name">
|
|
407
|
+
<Input
|
|
408
|
+
id="sc-last"
|
|
409
|
+
placeholder="Last name"
|
|
410
|
+
value={lastName}
|
|
411
|
+
onChange={(e) => setLastName(e.target.value)}
|
|
412
|
+
disabled={isSending}
|
|
413
|
+
/>
|
|
414
|
+
</Field>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
<Field id="sc-email" label="Email" required>
|
|
418
|
+
<Input
|
|
419
|
+
id="sc-email"
|
|
420
|
+
type="email"
|
|
421
|
+
placeholder="advisor@company.com.au"
|
|
422
|
+
value={email}
|
|
423
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
424
|
+
disabled={isSending}
|
|
425
|
+
/>
|
|
426
|
+
</Field>
|
|
427
|
+
|
|
428
|
+
<Field id="sc-ext-notes" label="Notes">
|
|
429
|
+
<Textarea
|
|
430
|
+
id="sc-ext-notes"
|
|
431
|
+
placeholder="Optional message…"
|
|
432
|
+
rows={3}
|
|
433
|
+
value={externalNotes}
|
|
434
|
+
onChange={(e) => setExternalNotes(e.target.value)}
|
|
435
|
+
disabled={isSending}
|
|
436
|
+
/>
|
|
437
|
+
</Field>
|
|
438
|
+
</TabsContent>
|
|
439
|
+
</Tabs>
|
|
440
|
+
|
|
441
|
+
<DialogFooter>
|
|
442
|
+
<Button
|
|
443
|
+
variant="outline"
|
|
444
|
+
onClick={() => handleOpenChange(false)}
|
|
445
|
+
disabled={isSending}
|
|
446
|
+
>
|
|
447
|
+
Cancel
|
|
448
|
+
</Button>
|
|
449
|
+
{tab === "internal" ? (
|
|
450
|
+
<Button
|
|
451
|
+
onClick={() => onShareInternal(selectedId!, internalNotes)}
|
|
452
|
+
disabled={!selectedId || isSending}
|
|
453
|
+
>
|
|
454
|
+
{isSending ? (
|
|
455
|
+
<>
|
|
456
|
+
<Spinner className="size-3.5" />
|
|
457
|
+
Sharing…
|
|
458
|
+
</>
|
|
459
|
+
) : (
|
|
460
|
+
"Share Contact"
|
|
461
|
+
)}
|
|
462
|
+
</Button>
|
|
463
|
+
) : (
|
|
464
|
+
<Button
|
|
465
|
+
onClick={() =>
|
|
466
|
+
onShareExternal({
|
|
467
|
+
firstName,
|
|
468
|
+
lastName,
|
|
469
|
+
email,
|
|
470
|
+
notes: externalNotes,
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
disabled={!email || isSending}
|
|
474
|
+
>
|
|
475
|
+
{isSending ? (
|
|
476
|
+
<>
|
|
477
|
+
<Spinner className="size-3.5" />
|
|
478
|
+
Sending…
|
|
479
|
+
</>
|
|
480
|
+
) : (
|
|
481
|
+
"Send Invite"
|
|
482
|
+
)}
|
|
483
|
+
</Button>
|
|
484
|
+
)}
|
|
485
|
+
</DialogFooter>
|
|
486
|
+
</DialogContent>
|
|
487
|
+
</Dialog>
|
|
488
|
+
);
|
|
489
|
+
}
|