@wealthx/shadcn 1.3.0 → 1.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wealthx/shadcn",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -11,9 +11,10 @@ export type {
11
11
  AccordionContentProps,
12
12
  } from "./ui/accordion";
13
13
 
14
- export { AdvisorCard } from "./ui/advisor-card";
14
+ export { AdvisorCard, AdvisorInviteCard } from "./ui/advisor-card";
15
15
  export type {
16
16
  AdvisorCardProps,
17
+ AdvisorInviteCardProps,
17
18
  AdvisorAppointmentStrip,
18
19
  } from "./ui/advisor-card";
19
20
 
@@ -10,6 +10,7 @@ import {
10
10
  Mail,
11
11
  MoreVertical,
12
12
  Phone,
13
+ Plus,
13
14
  } from "lucide-react";
14
15
  import type { AppointmentStatus } from "./appointment-time-slot-picker";
15
16
 
@@ -39,27 +40,34 @@ export interface AdvisorCardProps {
39
40
  phone: string;
40
41
  /** Email address */
41
42
  email: string;
43
+ /** Broker company name — shown as text fallback when no logo is provided */
44
+ companyName?: string;
42
45
  /** URL of the broker company logo — renders as avatar image */
43
46
  companyLogoUrl?: string;
44
- /** Initials shown in Avatar fallback when no companyLogoUrl is provided */
47
+ /** Explicit initials for the avatar fallback; derived from companyName if omitted */
45
48
  avatarInitials?: string;
46
49
  /** Whether this is the client's primary assigned advisor */
47
50
  isPrimary?: boolean;
48
51
  /**
49
- * Upcoming appointment data shown in the strip.
50
- * - `undefined` — strip is hidden (feature not applicable)
51
- * - `null` — empty state: "No upcoming appointments"
52
- * - objectappointment info with status colour
52
+ * Upcoming appointments shown as stacked strips.
53
+ * - `undefined` — strip section hidden
54
+ * - `null` or `[]` — empty state: "No upcoming appointments"
55
+ * - array of items one coloured strip per appointment
53
56
  */
54
- appointment?: AdvisorAppointmentStrip | null;
57
+ appointments?: AdvisorAppointmentStrip[] | null;
55
58
  /** Called when "Refer [name] to Others" is clicked */
56
59
  onRefer?: () => void;
57
60
  /** Called when the ⋮ overflow menu is clicked */
58
61
  onMoreOptions?: () => void;
59
62
  /** Called when "Book Appointment" is clicked */
60
63
  onBookAppointment?: () => void;
61
- /** Called when "View →" in the appointment strip is clicked */
62
- onViewAppointment?: () => void;
64
+ /** Called when "View →" in an appointment strip is clicked; receives the appointment index */
65
+ onViewAppointment?: (index: number) => void;
66
+ }
67
+
68
+ export interface AdvisorInviteCardProps {
69
+ /** Called when the "Add Advisor" CTA is clicked */
70
+ onInvite?: () => void;
63
71
  }
64
72
 
65
73
  // ---------------------------------------------------------------------------
@@ -99,15 +107,23 @@ export function AdvisorCard({
99
107
  role,
100
108
  phone,
101
109
  email,
110
+ companyName,
102
111
  companyLogoUrl,
103
112
  avatarInitials,
104
113
  isPrimary = false,
105
- appointment,
114
+ appointments,
106
115
  onRefer,
107
116
  onMoreOptions,
108
117
  onBookAppointment,
109
118
  onViewAppointment,
110
119
  }: AdvisorCardProps) {
120
+ const hasAppointments = appointments !== undefined;
121
+ const appointmentList =
122
+ appointments === null ||
123
+ (Array.isArray(appointments) && appointments.length === 0)
124
+ ? null
125
+ : appointments;
126
+
111
127
  return (
112
128
  <div className="flex flex-col border border-border bg-card">
113
129
  {/* ── Advisor info ── */}
@@ -117,7 +133,15 @@ export function AdvisorCard({
117
133
  <AvatarImage src={companyLogoUrl} alt={`${name} company logo`} />
118
134
  )}
119
135
  <AvatarFallback className="text-sm">
120
- {avatarInitials ?? name.slice(0, 2).toUpperCase()}
136
+ {avatarInitials ??
137
+ (companyName
138
+ ? companyName
139
+ .split(" ")
140
+ .map((w) => w[0])
141
+ .join("")
142
+ .slice(0, 2)
143
+ .toUpperCase()
144
+ : name.slice(0, 2).toUpperCase())}
121
145
  </AvatarFallback>
122
146
  </Avatar>
123
147
 
@@ -125,12 +149,12 @@ export function AdvisorCard({
125
149
  <p className="text-sm font-semibold leading-tight">{name}</p>
126
150
  <p className="text-sm text-muted-foreground">{role}</p>
127
151
  <div className="mt-2 flex flex-col gap-1">
128
- <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
129
- <Phone className="h-3 w-3 shrink-0" />
152
+ <div className="flex items-center gap-1.5 text-sm text-muted-foreground">
153
+ <Phone className="h-4 w-4 shrink-0" />
130
154
  <span>{phone}</span>
131
155
  </div>
132
- <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
133
- <Mail className="h-3 w-3 shrink-0" />
156
+ <div className="flex items-center gap-1.5 text-sm text-muted-foreground">
157
+ <Mail className="h-4 w-4 shrink-0" />
134
158
  <span>{email}</span>
135
159
  </div>
136
160
  </div>
@@ -151,49 +175,52 @@ export function AdvisorCard({
151
175
  </div>
152
176
  </div>
153
177
 
154
- {/* ── Appointment strip ── */}
155
- {appointment !== undefined && (
178
+ {/* ── Appointment strips ── */}
179
+ {hasAppointments && (
156
180
  <>
157
181
  <Separator />
158
- {appointment ? (
159
- /* Has appointment */
160
- <div
161
- className={`flex items-center gap-3 px-4 py-3 ${STRIP_BG[appointment.status]}`}
162
- >
163
- <CalendarCheck className="h-4 w-4 shrink-0 text-muted-foreground" />
164
- <div className="flex min-w-0 flex-1 flex-col gap-0.5">
165
- <div className="flex items-center gap-2">
166
- <Badge
167
- variant={STATUS_VARIANT[appointment.status]}
168
- className="text-[10px]"
169
- >
170
- {STATUS_LABEL[appointment.status]}
171
- </Badge>
172
- {appointment.appointmentType && (
173
- <span className="truncate text-xs text-muted-foreground">
174
- {appointment.appointmentType}
175
- </span>
182
+ {appointmentList ? (
183
+ appointmentList.map((appt, i) => (
184
+ <React.Fragment key={i}>
185
+ {i > 0 && <Separator />}
186
+ <div
187
+ className={`flex items-center gap-3 px-4 py-3 ${STRIP_BG[appt.status]}`}
188
+ >
189
+ <CalendarCheck className="h-4 w-4 shrink-0 text-muted-foreground" />
190
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5">
191
+ <div className="flex items-center gap-2">
192
+ <Badge
193
+ variant={STATUS_VARIANT[appt.status]}
194
+ className="text-[10px]"
195
+ >
196
+ {STATUS_LABEL[appt.status]}
197
+ </Badge>
198
+ {appt.appointmentType && (
199
+ <span className="truncate text-sm font-semibold">
200
+ {appt.appointmentType}
201
+ </span>
202
+ )}
203
+ </div>
204
+ <p className="whitespace-nowrap text-sm text-muted-foreground">
205
+ {appt.date} · {appt.timeStart}–{appt.timeEnd}
206
+ </p>
207
+ </div>
208
+ {onViewAppointment && (
209
+ <Button
210
+ variant="ghost"
211
+ size="sm"
212
+ className="h-7 shrink-0 gap-1 px-2 text-xs"
213
+ onClick={() => onViewAppointment(i)}
214
+ >
215
+ View
216
+ <ChevronRight className="h-3 w-3" />
217
+ </Button>
176
218
  )}
177
219
  </div>
178
- <p className="text-xs text-muted-foreground">
179
- {appointment.date} · {appointment.timeStart}–
180
- {appointment.timeEnd}
181
- </p>
182
- </div>
183
- {onViewAppointment && (
184
- <Button
185
- variant="ghost"
186
- size="sm"
187
- className="h-7 shrink-0 gap-1 px-2 text-xs"
188
- onClick={onViewAppointment}
189
- >
190
- View
191
- <ChevronRight className="h-3 w-3" />
192
- </Button>
193
- )}
194
- </div>
220
+ </React.Fragment>
221
+ ))
195
222
  ) : (
196
- /* No appointment */
223
+ /* Empty state */
197
224
  <div className="flex items-center gap-3 px-4 py-3">
198
225
  <Calendar className="h-4 w-4 shrink-0 text-muted-foreground" />
199
226
  <p className="flex-1 text-xs text-muted-foreground">
@@ -206,18 +233,24 @@ export function AdvisorCard({
206
233
 
207
234
  {/* ── Footer actions ── */}
208
235
  <Separator />
209
- <div className="flex items-center gap-2 px-4 py-3">
236
+ <div className="flex flex-col gap-2 px-4 py-3">
210
237
  {onBookAppointment && (
211
238
  <Button
212
239
  variant="outline-primary"
213
240
  size="sm"
241
+ className="w-full"
214
242
  onClick={onBookAppointment}
215
243
  >
216
244
  Book Appointment
217
245
  </Button>
218
246
  )}
219
247
  {onRefer && (
220
- <Button variant="outline" size="sm" onClick={onRefer}>
248
+ <Button
249
+ variant="outline"
250
+ size="sm"
251
+ className="w-full"
252
+ onClick={onRefer}
253
+ >
221
254
  Refer {name} to Others
222
255
  </Button>
223
256
  )}
@@ -225,3 +258,27 @@ export function AdvisorCard({
225
258
  </div>
226
259
  );
227
260
  }
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // AdvisorInviteCard
264
+ // ---------------------------------------------------------------------------
265
+
266
+ export function AdvisorInviteCard({ onInvite }: AdvisorInviteCardProps) {
267
+ return (
268
+ <Button
269
+ variant="ghost"
270
+ onClick={onInvite}
271
+ className="flex h-auto w-full flex-col items-center justify-center gap-3 border border-dashed border-border bg-card p-8 text-center transition-colors hover:bg-muted/40"
272
+ >
273
+ <div className="flex h-10 w-10 items-center justify-center border border-dashed border-muted-foreground/40">
274
+ <Plus className="h-5 w-5 text-muted-foreground" />
275
+ </div>
276
+ <div className="flex flex-col gap-1">
277
+ <p className="text-sm font-medium">Add Another Advisor</p>
278
+ <p className="text-xs text-muted-foreground">
279
+ Connect more advisors to your account
280
+ </p>
281
+ </div>
282
+ </Button>
283
+ );
284
+ }