@wealthx/shadcn 1.5.6 → 1.5.7
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 +141 -135
- package/CHANGELOG.md +6 -0
- package/dist/{chunk-2LLFNGJZ.mjs → chunk-3OOUI5TO.mjs} +1 -1
- package/dist/{chunk-AUEUTZIC.mjs → chunk-3QAQQBCM.mjs} +2 -2
- package/dist/chunk-3Z75IKFO.mjs +34 -0
- package/dist/{chunk-RAKBWNQH.mjs → chunk-5WCIGJ3E.mjs} +26 -65
- package/dist/chunk-65PZNG4Y.mjs +129 -0
- package/dist/{chunk-D447W45Z.mjs → chunk-7OLKVKJF.mjs} +41 -49
- package/dist/{chunk-UEREFDAE.mjs → chunk-AZGLSIHF.mjs} +1 -1
- package/dist/{chunk-QRVEI6J3.mjs → chunk-BQBSYM2I.mjs} +1 -3
- package/dist/{chunk-BFB3UH7V.mjs → chunk-C2QMHKLT.mjs} +1 -1
- package/dist/chunk-CEEVYRQA.mjs +61 -0
- package/dist/{chunk-VJ3GC7W3.mjs → chunk-CXGZSIQN.mjs} +4 -30
- package/dist/{chunk-VLELWBEW.mjs → chunk-E2BNCA6L.mjs} +1 -1
- package/dist/{chunk-46Q4335I.mjs → chunk-F73QFUZH.mjs} +10 -20
- package/dist/{chunk-Q35PNFJ7.mjs → chunk-FBNEIYSE.mjs} +1 -1
- package/dist/{chunk-HO6S3ECM.mjs → chunk-G4WLCKFV.mjs} +1 -1
- package/dist/{chunk-ODO6BUOF.mjs → chunk-HRHXZIX5.mjs} +1 -3
- package/dist/{chunk-IXR4BQSQ.mjs → chunk-I6QVWQCD.mjs} +1 -1
- package/dist/{chunk-Q5SGEIJV.mjs → chunk-IXKE6LHN.mjs} +1 -1
- package/dist/{chunk-OL65UQHQ.mjs → chunk-JCH2BG24.mjs} +26 -60
- package/dist/{chunk-PV3Y7QGK.mjs → chunk-LHWJQNLG.mjs} +3 -3
- package/dist/{chunk-JTG5R5YV.mjs → chunk-MS4PDUSU.mjs} +284 -58
- package/dist/{chunk-VFH632TB.mjs → chunk-SDNG4XL6.mjs} +1 -1
- package/dist/{chunk-DFL5CV75.mjs → chunk-T6MMCOX6.mjs} +1 -1
- package/dist/{chunk-6TX73WG7.mjs → chunk-TCE5L44O.mjs} +3 -2
- package/dist/{chunk-HROG643K.mjs → chunk-W5QJ57PU.mjs} +1 -1
- package/dist/{chunk-EW72FINW.mjs → chunk-XVZGXPIX.mjs} +1 -1
- package/dist/components/ui/about-you-form.mjs +3 -3
- package/dist/components/ui/add-column-modal.js +56 -227
- package/dist/components/ui/add-column-modal.mjs +1 -2
- package/dist/components/ui/ai-conversations.js +278 -58
- package/dist/components/ui/ai-conversations.mjs +1 -1
- package/dist/components/ui/appointment-action-dialogs.mjs +3 -3
- package/dist/components/ui/appointment-availability-settings.js +2 -28
- package/dist/components/ui/appointment-availability-settings.mjs +4 -4
- package/dist/components/ui/appointment-book-dialog.js +11 -21
- package/dist/components/ui/appointment-book-dialog.mjs +3 -3
- package/dist/components/ui/appointment-calendar-view.mjs +2 -2
- package/dist/components/ui/appointment-detail-sheet.mjs +4 -4
- package/dist/components/ui/appointment-upcoming-card.mjs +3 -3
- package/dist/components/ui/bank-statement-generate-dialog.mjs +4 -4
- package/dist/components/ui/calendar.mjs +2 -2
- package/dist/components/ui/dashboard-transactions-table.mjs +1 -1
- package/dist/components/ui/date-picker.mjs +3 -3
- package/dist/components/ui/financial-cards.mjs +2 -2
- package/dist/components/ui/financial-sections.mjs +3 -3
- package/dist/components/ui/integration-card.js +326 -0
- package/dist/components/ui/integration-card.mjs +11 -0
- package/dist/components/ui/kanban-column.js +151 -243
- package/dist/components/ui/kanban-column.mjs +3 -4
- package/dist/components/ui/loan-application-cards.mjs +1 -1
- package/dist/components/ui/onboarding-layout.js +65 -4
- package/dist/components/ui/onboarding-layout.mjs +6 -4
- package/dist/components/ui/opportunity-card.js +126 -216
- package/dist/components/ui/opportunity-card.mjs +2 -3
- package/dist/components/ui/opportunity-edit-modals.mjs +4 -4
- package/dist/components/ui/opportunity-summary-tab.mjs +6 -6
- package/dist/components/ui/pipeline-board.js +167 -261
- package/dist/components/ui/pipeline-board.mjs +4 -5
- package/dist/components/ui/pipeline-chart.js +128 -55
- package/dist/components/ui/pipeline-chart.mjs +2 -1
- package/dist/components/ui/pipeline-dialogs.mjs +4 -4
- package/dist/components/ui/savings-goal-modal.mjs +3 -3
- package/dist/components/ui/selectable-card.js +163 -0
- package/dist/components/ui/selectable-card.mjs +9 -0
- package/dist/components/ui/signup-shell.js +3 -2
- package/dist/components/ui/signup-shell.mjs +2 -2
- package/dist/components/ui/stepper.js +3 -2
- package/dist/components/ui/stepper.mjs +1 -1
- package/dist/index.js +2121 -1878
- package/dist/index.mjs +56 -46
- package/dist/styles.css +1 -1
- package/package.json +11 -1
- package/src/components/index.tsx +13 -1
- package/src/components/ui/add-column-modal.tsx +9 -58
- package/src/components/ui/ai-conversations.tsx +308 -42
- package/src/components/ui/appointment-availability-settings.tsx +2 -35
- package/src/components/ui/appointment-book-dialog.tsx +25 -48
- package/src/components/ui/integration-card.tsx +88 -0
- package/src/components/ui/kanban-column.tsx +0 -7
- package/src/components/ui/onboarding-layout.tsx +102 -1
- package/src/components/ui/opportunity-card.tsx +5 -53
- package/src/components/ui/pipeline-board.tsx +0 -3
- package/src/components/ui/pipeline-chart.tsx +47 -53
- package/src/components/ui/selectable-card.tsx +37 -0
- package/src/components/ui/stepper.tsx +3 -2
- package/src/lib/format-date.ts +6 -6
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +2 -0
- package/dist/chunk-2P7HP7LR.mjs +0 -68
|
@@ -27,7 +27,7 @@ import { Toggle } from "./toggle";
|
|
|
27
27
|
import { Badge } from "./badge";
|
|
28
28
|
import { Avatar, AvatarFallback } from "./avatar";
|
|
29
29
|
import { CalendarCheck, MapPin, Phone, Users, Video } from "lucide-react";
|
|
30
|
-
import { formatDateLong } from "
|
|
30
|
+
import { formatDateLong } from "../../lib/format-date";
|
|
31
31
|
import {
|
|
32
32
|
AppointmentSlotSection,
|
|
33
33
|
type AppointmentMeetingFormat,
|
|
@@ -259,16 +259,9 @@ const FORMAT_OPTIONS: {
|
|
|
259
259
|
},
|
|
260
260
|
];
|
|
261
261
|
|
|
262
|
-
// Pre-baked references — FORMAT_OPTIONS is a module constant so these are stable
|
|
263
|
-
const FORMAT_CALL = FORMAT_OPTIONS[0];
|
|
264
|
-
const FORMAT_OFFLINE = FORMAT_OPTIONS[3];
|
|
265
|
-
// Icon shared by the "Online Meeting" collapsed option in client mode
|
|
266
|
-
const ONLINE_MEETING_ICON = <Video className="h-4 w-4" />;
|
|
267
|
-
|
|
268
262
|
function MeetingFormatSection({
|
|
269
263
|
format,
|
|
270
264
|
onFormatChange,
|
|
271
|
-
options = FORMAT_OPTIONS,
|
|
272
265
|
offlineLocation,
|
|
273
266
|
onOfflineLocationChange,
|
|
274
267
|
customAddress,
|
|
@@ -279,7 +272,6 @@ function MeetingFormatSection({
|
|
|
279
272
|
}: {
|
|
280
273
|
format: AppointmentMeetingFormat;
|
|
281
274
|
onFormatChange: (f: AppointmentMeetingFormat) => void;
|
|
282
|
-
options?: typeof FORMAT_OPTIONS;
|
|
283
275
|
offlineLocation: AppointmentOfflineLocation;
|
|
284
276
|
onOfflineLocationChange: (l: AppointmentOfflineLocation) => void;
|
|
285
277
|
customAddress: string;
|
|
@@ -291,7 +283,7 @@ function MeetingFormatSection({
|
|
|
291
283
|
return (
|
|
292
284
|
<div className="flex flex-col gap-2">
|
|
293
285
|
<div className="flex gap-2">
|
|
294
|
-
{
|
|
286
|
+
{FORMAT_OPTIONS.map((opt) => (
|
|
295
287
|
<Toggle
|
|
296
288
|
key={opt.value}
|
|
297
289
|
variant="outline"
|
|
@@ -463,22 +455,6 @@ export function AppointmentBookDialog({
|
|
|
463
455
|
// Guest form is shown in client mode when at least one identity field is missing.
|
|
464
456
|
const showGuestForm = isClientMode && !(guestInfo?.name && guestInfo?.email);
|
|
465
457
|
|
|
466
|
-
// In client mode with a preset online platform, collapse Google Meet / MS Teams
|
|
467
|
-
// into a single "Online Meeting" option so the client sees 3 choices:
|
|
468
|
-
// Call | Online Meeting | Offline — without knowing the specific platform.
|
|
469
|
-
const visibleFormatOptions: typeof FORMAT_OPTIONS =
|
|
470
|
-
isClientMode && defaultMeetingFormat
|
|
471
|
-
? [
|
|
472
|
-
FORMAT_CALL,
|
|
473
|
-
{
|
|
474
|
-
value: defaultMeetingFormat,
|
|
475
|
-
label: "Online Meeting",
|
|
476
|
-
icon: ONLINE_MEETING_ICON,
|
|
477
|
-
},
|
|
478
|
-
FORMAT_OFFLINE,
|
|
479
|
-
]
|
|
480
|
-
: FORMAT_OPTIONS;
|
|
481
|
-
|
|
482
458
|
const disabledDayOfWeek = React.useMemo(
|
|
483
459
|
() =>
|
|
484
460
|
schedule
|
|
@@ -578,7 +554,7 @@ export function AppointmentBookDialog({
|
|
|
578
554
|
|
|
579
555
|
return (
|
|
580
556
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
581
|
-
<DialogContent size="2xl">
|
|
557
|
+
<DialogContent size="2xl" align="top">
|
|
582
558
|
{submittedBooking ? (
|
|
583
559
|
<BookingConfirmationScreen
|
|
584
560
|
booking={submittedBooking}
|
|
@@ -691,26 +667,23 @@ export function AppointmentBookDialog({
|
|
|
691
667
|
</>
|
|
692
668
|
)}
|
|
693
669
|
|
|
694
|
-
{/* Format picker
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
isClientMode={isClientMode}
|
|
712
|
-
/>
|
|
713
|
-
</div>
|
|
670
|
+
{/* Format picker — hidden in client mode when broker pre-set a platform */}
|
|
671
|
+
{!(isClientMode && defaultMeetingFormat) && (
|
|
672
|
+
<div className="flex flex-col gap-1.5">
|
|
673
|
+
<Label>Meeting format</Label>
|
|
674
|
+
<MeetingFormatSection
|
|
675
|
+
format={meetingFormat}
|
|
676
|
+
onFormatChange={setMeetingFormat}
|
|
677
|
+
offlineLocation={offlineLocation}
|
|
678
|
+
onOfflineLocationChange={setOfflineLocation}
|
|
679
|
+
customAddress={customAddress}
|
|
680
|
+
onCustomAddressChange={setCustomAddress}
|
|
681
|
+
advisorOfficeAddress={advisorOfficeAddress}
|
|
682
|
+
clientHomeAddress={clientHomeAddress}
|
|
683
|
+
isClientMode={isClientMode}
|
|
684
|
+
/>
|
|
685
|
+
</div>
|
|
686
|
+
)}
|
|
714
687
|
|
|
715
688
|
{/* Phone selection — advisor mode, Call format, client has phones */}
|
|
716
689
|
{!isClientMode &&
|
|
@@ -774,7 +747,11 @@ export function AppointmentBookDialog({
|
|
|
774
747
|
{date ? (
|
|
775
748
|
<div className="flex flex-col gap-3">
|
|
776
749
|
<p className="text-xs text-muted-foreground">
|
|
777
|
-
{
|
|
750
|
+
{date.toLocaleDateString("en-AU", {
|
|
751
|
+
weekday: "long",
|
|
752
|
+
day: "numeric",
|
|
753
|
+
month: "long",
|
|
754
|
+
})}
|
|
778
755
|
</p>
|
|
779
756
|
<AppointmentSlotSection
|
|
780
757
|
label="Morning"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Check } from "lucide-react";
|
|
3
|
+
import { Badge } from "./badge";
|
|
4
|
+
import { Button } from "./button";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
|
|
7
|
+
export interface IntegrationCardProps {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
/** Image src for the integration logo (e.g. aggregator logo) */
|
|
11
|
+
logoSrc?: string;
|
|
12
|
+
/** Fallback icon node shown when no logoSrc is provided but a logo area is desired */
|
|
13
|
+
logoFallback?: React.ReactNode;
|
|
14
|
+
connected?: boolean;
|
|
15
|
+
onConnect?: () => void;
|
|
16
|
+
onDisconnect?: () => void;
|
|
17
|
+
/** Extra content rendered between description and action button (e.g. inbox assignment Select) */
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function IntegrationCard({
|
|
23
|
+
name,
|
|
24
|
+
description,
|
|
25
|
+
logoSrc,
|
|
26
|
+
logoFallback,
|
|
27
|
+
connected = false,
|
|
28
|
+
onConnect,
|
|
29
|
+
onDisconnect,
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
}: IntegrationCardProps) {
|
|
33
|
+
const hasLogo = Boolean(logoSrc || logoFallback);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex flex-col gap-3 border border-border p-4",
|
|
39
|
+
connected && "bg-muted/20",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
<div className="flex items-center gap-2">
|
|
44
|
+
{hasLogo && (
|
|
45
|
+
<div className="flex shrink-0 items-center">
|
|
46
|
+
{logoSrc ? (
|
|
47
|
+
<img
|
|
48
|
+
src={logoSrc}
|
|
49
|
+
alt={`${name} logo`}
|
|
50
|
+
className="h-8 w-16 object-contain object-left"
|
|
51
|
+
/>
|
|
52
|
+
) : (
|
|
53
|
+
<div className="flex h-9 w-9 items-center justify-center">
|
|
54
|
+
{logoFallback}
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
)}
|
|
59
|
+
<span className="text-label-medium">{name}</span>
|
|
60
|
+
{connected && (
|
|
61
|
+
<Badge variant="success" className="gap-1">
|
|
62
|
+
<Check className="h-3 w-3" />
|
|
63
|
+
Connected
|
|
64
|
+
</Badge>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<p className="text-body-small text-muted-foreground">{description}</p>
|
|
69
|
+
|
|
70
|
+
{children}
|
|
71
|
+
|
|
72
|
+
{!connected && onConnect && (
|
|
73
|
+
<div className="flex justify-end">
|
|
74
|
+
<Button variant="outline" size="sm" onClick={onConnect}>
|
|
75
|
+
Connect
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
{connected && onDisconnect && (
|
|
80
|
+
<div className="flex justify-end">
|
|
81
|
+
<Button variant="outline" size="sm" onClick={onDisconnect}>
|
|
82
|
+
Disconnect
|
|
83
|
+
</Button>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -101,7 +101,6 @@ export interface KanbanColumnProps {
|
|
|
101
101
|
onCardClick?: (opportunityId: string) => void;
|
|
102
102
|
onViewDetails?: (opportunityId: string) => void;
|
|
103
103
|
onChangePriority?: (opportunityId: string) => void;
|
|
104
|
-
onLaunchAssistant?: (opportunityId: string) => void;
|
|
105
104
|
onPutOnHold?: (opportunityId: string) => void;
|
|
106
105
|
onDeleteOpportunity?: (opportunityId: string) => void;
|
|
107
106
|
/** Fires when a card is dropped onto this column (HTML5 DnD). */
|
|
@@ -150,7 +149,6 @@ export function KanbanColumn({
|
|
|
150
149
|
onCardClick,
|
|
151
150
|
onViewDetails,
|
|
152
151
|
onChangePriority,
|
|
153
|
-
onLaunchAssistant,
|
|
154
152
|
onPutOnHold,
|
|
155
153
|
onDeleteOpportunity,
|
|
156
154
|
onCardDrop,
|
|
@@ -344,11 +342,6 @@ export function KanbanColumn({
|
|
|
344
342
|
? () => onChangePriority(opp.id)
|
|
345
343
|
: undefined
|
|
346
344
|
}
|
|
347
|
-
onLaunchAssistant={
|
|
348
|
-
onLaunchAssistant
|
|
349
|
-
? () => onLaunchAssistant(opp.id)
|
|
350
|
-
: undefined
|
|
351
|
-
}
|
|
352
345
|
onPutOnHold={
|
|
353
346
|
onPutOnHold ? () => onPutOnHold(opp.id) : undefined
|
|
354
347
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Button } from "./button";
|
|
3
|
-
import { Stepper, Step, StepItem } from "./stepper";
|
|
3
|
+
import { Stepper, Step, StepItem, StepIndicator, StepLabel } from "./stepper";
|
|
4
4
|
import { cn } from "../../lib/utils";
|
|
5
5
|
|
|
6
6
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -8,6 +8,7 @@ import { cn } from "../../lib/utils";
|
|
|
8
8
|
export type OnboardingStep = {
|
|
9
9
|
id: string;
|
|
10
10
|
title: string;
|
|
11
|
+
description?: string;
|
|
11
12
|
isOptional?: boolean;
|
|
12
13
|
isLocked?: boolean;
|
|
13
14
|
};
|
|
@@ -31,6 +32,106 @@ export type OnboardingLayoutProps = {
|
|
|
31
32
|
|
|
32
33
|
// ─── OnboardingLayout ─────────────────────────────────────────────────────────
|
|
33
34
|
|
|
35
|
+
// ─── OnboardingPanelLayoutProps ───────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
export type OnboardingPanelLayoutProps = {
|
|
38
|
+
title: string;
|
|
39
|
+
subtitle?: string;
|
|
40
|
+
steps: OnboardingStep[];
|
|
41
|
+
currentStepIndex: number;
|
|
42
|
+
canProceed?: boolean;
|
|
43
|
+
onBack: () => void;
|
|
44
|
+
onNext: () => void;
|
|
45
|
+
onSkip?: () => void;
|
|
46
|
+
className?: string;
|
|
47
|
+
children: React.ReactNode;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ─── OnboardingPanelLayout ────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function OnboardingPanelLayout({
|
|
53
|
+
title,
|
|
54
|
+
subtitle,
|
|
55
|
+
steps,
|
|
56
|
+
currentStepIndex,
|
|
57
|
+
canProceed = true,
|
|
58
|
+
onBack,
|
|
59
|
+
onNext,
|
|
60
|
+
onSkip,
|
|
61
|
+
className,
|
|
62
|
+
children,
|
|
63
|
+
}: OnboardingPanelLayoutProps) {
|
|
64
|
+
const isFirstStep = currentStepIndex === 0;
|
|
65
|
+
const isLastStep = currentStepIndex === steps.length - 1;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className={cn("flex h-screen bg-background font-sans", className)}>
|
|
69
|
+
{/* ── Left panel — vertical stepper ── */}
|
|
70
|
+
<div className="flex w-72 shrink-0 flex-col border-r border-border bg-muted/20 p-8">
|
|
71
|
+
<div className="flex flex-col gap-1 mb-8">
|
|
72
|
+
<span className="text-h1">{title}</span>
|
|
73
|
+
{subtitle && (
|
|
74
|
+
<span className="text-body-medium text-muted-foreground">
|
|
75
|
+
{subtitle}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
{steps.length > 1 && (
|
|
80
|
+
<Stepper
|
|
81
|
+
activeStep={currentStepIndex}
|
|
82
|
+
orientation="vertical"
|
|
83
|
+
className="flex-1"
|
|
84
|
+
>
|
|
85
|
+
{steps.map((step) => (
|
|
86
|
+
<Step key={step.id} className="flex-1">
|
|
87
|
+
<StepIndicator />
|
|
88
|
+
<div className="flex flex-1 flex-col">
|
|
89
|
+
<StepLabel
|
|
90
|
+
description={
|
|
91
|
+
step.isLocked ? "Upgrade to unlock" : step.description
|
|
92
|
+
}
|
|
93
|
+
>
|
|
94
|
+
{step.title}
|
|
95
|
+
</StepLabel>
|
|
96
|
+
</div>
|
|
97
|
+
</Step>
|
|
98
|
+
))}
|
|
99
|
+
</Stepper>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{/* ── Right panel — content + footer ── */}
|
|
104
|
+
<div className="flex min-h-0 flex-1 flex-col">
|
|
105
|
+
<div className="flex-1 overflow-y-auto px-10 py-8">
|
|
106
|
+
<div className="max-w-2xl">{children}</div>
|
|
107
|
+
</div>
|
|
108
|
+
<div className="flex items-center gap-2 border-t border-border px-10 py-4">
|
|
109
|
+
<Button
|
|
110
|
+
variant="outline-primary"
|
|
111
|
+
size="sm"
|
|
112
|
+
onClick={onNext}
|
|
113
|
+
disabled={!canProceed}
|
|
114
|
+
>
|
|
115
|
+
{isLastStep ? "Finish Setup" : "Next"}
|
|
116
|
+
</Button>
|
|
117
|
+
{!isFirstStep && (
|
|
118
|
+
<Button variant="outline-secondary" size="sm" onClick={onBack}>
|
|
119
|
+
Back
|
|
120
|
+
</Button>
|
|
121
|
+
)}
|
|
122
|
+
{onSkip && (
|
|
123
|
+
<Button variant="ghost" size="sm" onClick={onSkip}>
|
|
124
|
+
Skip for now
|
|
125
|
+
</Button>
|
|
126
|
+
)}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── OnboardingLayout ─────────────────────────────────────────────────────────
|
|
134
|
+
|
|
34
135
|
export function OnboardingLayout({
|
|
35
136
|
steps,
|
|
36
137
|
currentStepIndex,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
MoreVertical,
|
|
10
10
|
Clock,
|
|
11
11
|
ArrowRight,
|
|
12
|
-
Bot,
|
|
13
12
|
ChevronDown,
|
|
14
13
|
ChevronRight,
|
|
15
14
|
} from "lucide-react";
|
|
@@ -34,12 +33,6 @@ import {
|
|
|
34
33
|
} from "@/components/ui/accordion";
|
|
35
34
|
import { TaskCheckItem } from "@/components/ui/pipeline-primitives";
|
|
36
35
|
import { Progress } from "@/components/ui/progress";
|
|
37
|
-
import {
|
|
38
|
-
Tooltip,
|
|
39
|
-
TooltipContent,
|
|
40
|
-
TooltipProvider,
|
|
41
|
-
TooltipTrigger,
|
|
42
|
-
} from "@/components/ui/tooltip";
|
|
43
36
|
|
|
44
37
|
/**
|
|
45
38
|
* OpportunityCard — WealthX DS (L3 Card)
|
|
@@ -63,8 +56,6 @@ export interface OpportunityTask {
|
|
|
63
56
|
id: string;
|
|
64
57
|
title: string;
|
|
65
58
|
completed: boolean;
|
|
66
|
-
aiAgentId?: string | null;
|
|
67
|
-
aiAgentName?: string | null;
|
|
68
59
|
}
|
|
69
60
|
|
|
70
61
|
export interface OpportunityCardProps {
|
|
@@ -121,7 +112,6 @@ export interface OpportunityCardProps {
|
|
|
121
112
|
onTaskToggle?: (taskId: string) => void;
|
|
122
113
|
onMarkAsDone?: () => void;
|
|
123
114
|
onMoveToNextStage?: () => void;
|
|
124
|
-
onLaunchAssistant?: () => void;
|
|
125
115
|
onChangePriority?: () => void;
|
|
126
116
|
onDelete?: () => void;
|
|
127
117
|
onPutOnHold?: () => void;
|
|
@@ -226,10 +216,6 @@ function TaskViewCard({
|
|
|
226
216
|
const hasTasks = totalCount > 0;
|
|
227
217
|
const allDone = hasTasks && completedCount === totalCount;
|
|
228
218
|
|
|
229
|
-
// Find the next pending task to surface AI agent info
|
|
230
|
-
const nextPendingTask = tasks.find((t) => !t.completed);
|
|
231
|
-
const agentName = nextPendingTask?.aiAgentName ?? null;
|
|
232
|
-
|
|
233
219
|
const [subtasksExpanded, setSubtasksExpanded] = useState(false);
|
|
234
220
|
|
|
235
221
|
const hasMenu = onViewDetails || onChangePriority || onPutOnHold || onDelete;
|
|
@@ -262,22 +248,7 @@ function TaskViewCard({
|
|
|
262
248
|
);
|
|
263
249
|
} else if (nextTask) {
|
|
264
250
|
taskHeader = (
|
|
265
|
-
|
|
266
|
-
<div className="flex items-center gap-1.5">
|
|
267
|
-
{agentName && (
|
|
268
|
-
<Bot
|
|
269
|
-
className="size-3.5 shrink-0 text-primary"
|
|
270
|
-
aria-hidden="true"
|
|
271
|
-
/>
|
|
272
|
-
)}
|
|
273
|
-
<p className="truncate text-sm font-semibold leading-snug">
|
|
274
|
-
{nextTask}
|
|
275
|
-
</p>
|
|
276
|
-
</div>
|
|
277
|
-
{agentName && (
|
|
278
|
-
<p className="mt-0.5 text-xs text-muted-foreground">{agentName}</p>
|
|
279
|
-
)}
|
|
280
|
-
</>
|
|
251
|
+
<p className="truncate text-sm font-semibold leading-snug">{nextTask}</p>
|
|
281
252
|
);
|
|
282
253
|
} else {
|
|
283
254
|
taskHeader = (
|
|
@@ -360,7 +331,6 @@ function TaskViewCard({
|
|
|
360
331
|
key={task.id}
|
|
361
332
|
title={task.title}
|
|
362
333
|
completed={task.completed}
|
|
363
|
-
aiAgentName={task.aiAgentName}
|
|
364
334
|
onToggle={() => onTaskToggle?.(task.id)}
|
|
365
335
|
size="xs"
|
|
366
336
|
/>
|
|
@@ -486,7 +456,6 @@ export function OpportunityCard({
|
|
|
486
456
|
onTaskToggle,
|
|
487
457
|
onMarkAsDone,
|
|
488
458
|
onMoveToNextStage,
|
|
489
|
-
onLaunchAssistant,
|
|
490
459
|
onChangePriority,
|
|
491
460
|
onDelete,
|
|
492
461
|
onPutOnHold,
|
|
@@ -596,25 +565,6 @@ export function OpportunityCard({
|
|
|
596
565
|
</div>
|
|
597
566
|
|
|
598
567
|
<div className="flex items-center gap-1 -mr-1 -mt-1" onClick={stopProp}>
|
|
599
|
-
{onLaunchAssistant && (
|
|
600
|
-
<TooltipProvider delay={0}>
|
|
601
|
-
<Tooltip>
|
|
602
|
-
<TooltipTrigger asChild>
|
|
603
|
-
<Button
|
|
604
|
-
type="button"
|
|
605
|
-
variant="ghost"
|
|
606
|
-
size="icon"
|
|
607
|
-
className="size-7 shrink-0"
|
|
608
|
-
onClick={onLaunchAssistant}
|
|
609
|
-
aria-label="Launch AI"
|
|
610
|
-
>
|
|
611
|
-
<Bot className="size-4" />
|
|
612
|
-
</Button>
|
|
613
|
-
</TooltipTrigger>
|
|
614
|
-
<TooltipContent>Launch AI</TooltipContent>
|
|
615
|
-
</Tooltip>
|
|
616
|
-
</TooltipProvider>
|
|
617
|
-
)}
|
|
618
568
|
{hasMenu && (
|
|
619
569
|
<DropdownMenu>
|
|
620
570
|
<DropdownMenuTrigger
|
|
@@ -762,7 +712,6 @@ export function OpportunityCard({
|
|
|
762
712
|
key={task.id}
|
|
763
713
|
title={task.title}
|
|
764
714
|
completed={task.completed}
|
|
765
|
-
aiAgentName={task.aiAgentName}
|
|
766
715
|
onToggle={
|
|
767
716
|
onTaskToggle ? () => onTaskToggle(task.id) : undefined
|
|
768
717
|
}
|
|
@@ -926,7 +875,10 @@ export function LeadCard({
|
|
|
926
875
|
Or the link below to fill out the loan application directly.
|
|
927
876
|
<br />
|
|
928
877
|
<a
|
|
929
|
-
href={`https://${loanApplicationUrl.replace(
|
|
878
|
+
href={`https://${loanApplicationUrl.replace(
|
|
879
|
+
/^https?:\/\//,
|
|
880
|
+
"",
|
|
881
|
+
)}`}
|
|
930
882
|
target="_blank"
|
|
931
883
|
rel="noreferrer"
|
|
932
884
|
className="text-primary underline-offset-2 hover:underline"
|
|
@@ -95,7 +95,6 @@ export interface PipelineBoardProps {
|
|
|
95
95
|
onMoveToNextStage?: (opportunityId: string) => void;
|
|
96
96
|
onViewDetails?: (opportunityId: string) => void;
|
|
97
97
|
onChangePriority?: (opportunityId: string) => void;
|
|
98
|
-
onLaunchAssistant?: (opportunityId: string) => void;
|
|
99
98
|
onPutOnHold?: (opportunityId: string) => void;
|
|
100
99
|
onDeleteOpportunity?: (opportunityId: string) => void;
|
|
101
100
|
submittingOpportunityId?: string | null;
|
|
@@ -210,7 +209,6 @@ export function PipelineBoard({
|
|
|
210
209
|
onMoveToNextStage,
|
|
211
210
|
onViewDetails,
|
|
212
211
|
onChangePriority,
|
|
213
|
-
onLaunchAssistant,
|
|
214
212
|
onPutOnHold,
|
|
215
213
|
onDeleteOpportunity,
|
|
216
214
|
submittingOpportunityId,
|
|
@@ -268,7 +266,6 @@ export function PipelineBoard({
|
|
|
268
266
|
onSendLoanApplication={col.onSendLoanApplication}
|
|
269
267
|
onViewDetails={onViewDetails}
|
|
270
268
|
onChangePriority={onChangePriority}
|
|
271
|
-
onLaunchAssistant={onLaunchAssistant}
|
|
272
269
|
onPutOnHold={onPutOnHold}
|
|
273
270
|
onDeleteOpportunity={onDeleteOpportunity}
|
|
274
271
|
submittingOpportunityId={submittingOpportunityId}
|
|
@@ -2,6 +2,12 @@ import * as React from "react";
|
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
import { useThemeVars } from "@/lib/theme-provider";
|
|
4
4
|
import { formatCurrencyAbbrev } from "@/lib/format-currency";
|
|
5
|
+
import {
|
|
6
|
+
Tooltip,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipProvider,
|
|
9
|
+
TooltipTrigger,
|
|
10
|
+
} from "./tooltip";
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* PipelineChart — WealthX DS (L4 Section)
|
|
@@ -65,8 +71,6 @@ export function PipelineChart({
|
|
|
65
71
|
}: PipelineChartProps) {
|
|
66
72
|
const themeVars = useThemeVars();
|
|
67
73
|
const [activeId, setActiveId] = React.useState<string | null>(null);
|
|
68
|
-
const [tooltipX, setTooltipX] = React.useState(0);
|
|
69
|
-
const barRef = React.useRef<HTMLDivElement>(null);
|
|
70
74
|
|
|
71
75
|
const nonEmpty = stages.filter((s) => s.value > 0);
|
|
72
76
|
const total = nonEmpty.reduce((sum, s) => sum + s.value, 0);
|
|
@@ -86,63 +90,53 @@ export function PipelineChart({
|
|
|
86
90
|
);
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
const activeStage = activeId ? stages.find((s) => s.id === activeId) : null;
|
|
90
|
-
|
|
91
93
|
return (
|
|
92
94
|
<div
|
|
93
95
|
className={cn("flex flex-col gap-3", className)}
|
|
94
96
|
data-slot="pipeline-chart"
|
|
95
97
|
style={themeVars as React.CSSProperties}
|
|
96
98
|
>
|
|
97
|
-
{/* Stacked bar */}
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
style={{ left: tooltipX, transform: "translateX(-50%)" }}
|
|
139
|
-
>
|
|
140
|
-
<span className="font-medium">{activeStage.name}</span>
|
|
141
|
-
{" — "}
|
|
142
|
-
{formatValue(activeStage.value)}
|
|
143
|
-
</div>
|
|
144
|
-
)}
|
|
145
|
-
</div>
|
|
99
|
+
{/* Stacked bar — TooltipContent uses a portal so it is never clipped */}
|
|
100
|
+
<TooltipProvider>
|
|
101
|
+
<div
|
|
102
|
+
className="relative flex w-full overflow-hidden"
|
|
103
|
+
style={{ height: barHeight }}
|
|
104
|
+
>
|
|
105
|
+
{nonEmpty.map((stage, i) => {
|
|
106
|
+
const pct = (stage.value / total) * 100;
|
|
107
|
+
const color =
|
|
108
|
+
stage.color ?? FALLBACK_COLORS[i % FALLBACK_COLORS.length];
|
|
109
|
+
const isActive = activeId === stage.id;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<Tooltip key={stage.id}>
|
|
113
|
+
<TooltipTrigger
|
|
114
|
+
render={
|
|
115
|
+
<div
|
|
116
|
+
role="img"
|
|
117
|
+
className="relative h-full cursor-pointer transition-opacity"
|
|
118
|
+
style={{
|
|
119
|
+
width: `${pct}%`,
|
|
120
|
+
backgroundColor: color,
|
|
121
|
+
opacity: activeId && !isActive ? 0.5 : 1,
|
|
122
|
+
minWidth: 2,
|
|
123
|
+
}}
|
|
124
|
+
onMouseEnter={() => setActiveId(stage.id)}
|
|
125
|
+
onMouseLeave={() => setActiveId(null)}
|
|
126
|
+
aria-label={`${stage.name}: ${formatValue(stage.value)}`}
|
|
127
|
+
/>
|
|
128
|
+
}
|
|
129
|
+
/>
|
|
130
|
+
<TooltipContent side="top">
|
|
131
|
+
<span className="font-medium">{stage.name}</span>
|
|
132
|
+
{" — "}
|
|
133
|
+
{formatValue(stage.value)}
|
|
134
|
+
</TooltipContent>
|
|
135
|
+
</Tooltip>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
</TooltipProvider>
|
|
146
140
|
|
|
147
141
|
{/* Legend */}
|
|
148
142
|
<div className="flex flex-wrap gap-x-4 gap-y-1.5">
|