@burdenoff/microfe-timecampus 2026.703.2 → 2026.703.4

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.
@@ -1,51 +1,65 @@
1
1
  import { getInitials as e } from "../../utils/helpers.js";
2
2
  import { StatCard as t } from "../../components/StatCard.js";
3
3
  import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
4
- import { useCreateClientPortalConfigMutation as r, useDeleteClientPortalConfigMutation as ee, useGetClientPortalStatsQuery as te, useListClientPortalConfigsQuery as i, useRegenerateClientPortalShareTokenMutation as ne, useToggleClientPortalMutation as re, useUpdateClientPortalConfigMutation as ie } from "../../../generated/wspace-operations.js";
5
- import { PageLayout as a } from "../../components/PageLayout.js";
6
- import { EmptyState as ae } from "../../components/EmptyState.js";
7
- import { Modal as o } from "../../components/Modal.js";
8
- import { StatusBadge as oe } from "../../components/StatusBadge.js";
9
- import { ToastContainer as se, useToast as ce } from "../../components/Toast.js";
10
- import { useState as s } from "react";
11
- import { Building2 as c, Check as l, DollarSign as u, FolderOpen as d, Link2 as f, Pencil as p, Plus as le, Power as ue, RefreshCw as de, Trash2 as fe } from "lucide-react";
12
- import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
4
+ import { useCreateClientPortalConfigMutation as r, useDeleteClientPortalConfigMutation as i, useGetClientPortalStatsQuery as a, useListClientPortalConfigsQuery as o, useRegenerateClientPortalShareTokenMutation as ee, useToggleClientPortalMutation as te, useUpdateClientPortalConfigMutation as s } from "../../../generated/wspace-operations.js";
5
+ import { PageLayout as c } from "../../components/PageLayout.js";
6
+ import { EmptyState as ne } from "../../components/EmptyState.js";
7
+ import { Modal as l } from "../../components/Modal.js";
8
+ import { StatusBadge as re } from "../../components/StatusBadge.js";
9
+ import { ToastContainer as ie, useToast as ae } from "../../components/Toast.js";
10
+ import { useState as u } from "react";
11
+ import { Building2 as oe, Check as se, DollarSign as ce, FolderOpen as le, Link2 as ue, Pencil as de, Plus as fe, Power as pe, RefreshCw as me, Trash2 as he } from "lucide-react";
12
+ import { gql as d } from "@apollo/client";
13
+ import { useApolloClient as ge } from "@apollo/client/react";
14
+ import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
15
+ import { uploadBlobToFiles as h } from "@burdenoff/fe-libs/storage";
13
16
  //#region src/timecampus/pages/client-portal/ClientPortalPage.tsx
14
- var _ = 25, v = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary";
15
- function pe(e) {
17
+ var g = 25, _ = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary", v = "mt-1 block w-full text-sm text-text-secondary file:mr-3 file:rounded-md file:border-0 file:bg-action-primary-bg file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-action-primary-text hover:file:bg-action-primary-bg/90", y = d`
18
+ query TimeCampus_GetFileDownloadUrl($id: ID!, $expiresIn: Int) {
19
+ getFileDownloadUrl(id: $id, expiresIn: $expiresIn)
20
+ }
21
+ `, _e = d`
22
+ mutation TimeCampusClient_EnsureFolder($path: String!) {
23
+ ensureFolderByPath(path: $path) {
24
+ id
25
+ }
26
+ }
27
+ `;
28
+ function ve(e) {
16
29
  return `${typeof window < "u" ? window.location.origin : ""}/portal/${e}`;
17
30
  }
18
- var y = {
31
+ var b = {
19
32
  clientName: "",
20
33
  contactEmail: "",
21
34
  contactPhone: "",
22
- description: ""
35
+ description: "",
36
+ logoUrl: ""
23
37
  };
24
- function b() {
25
- let { toasts: b, addToast: x, dismissToast: me } = ce(), [he, S] = s(null), { data: C, loading: w, error: T, refetch: E, fetchMore: D } = i({
38
+ function x() {
39
+ let { toasts: d, addToast: x, dismissToast: ye } = ae(), S = ge(), [be, C] = u(null), [w, T] = u(!1), { data: E, loading: D, error: O, refetch: k, fetchMore: A } = o({
26
40
  variables: {
27
- limit: _,
41
+ limit: g,
28
42
  offset: 0
29
43
  },
30
44
  notifyOnNetworkStatusChange: !0
31
- }), { data: O, refetch: k } = te(), [A, { loading: j }] = ne(), [M, { loading: N }] = r(), [P, { loading: F }] = ie(), [I, { loading: L }] = ee(), [R] = re(), z = C?.clientPortalConfigs?.items ?? [], ge = C?.clientPortalConfigs?.hasMore ?? !1, B = O?.clientPortalStats, [V, H] = s(!1), [_e, U] = s(!1), [W, G] = s(null), [K, q] = s(y), [J, Y] = s(null), [ve, X] = s(null), Z = async () => {
32
- await Promise.all([E(), k()]);
33
- }, ye = async (e) => {
45
+ }), { data: j, refetch: M } = a(), [N, { loading: P }] = ee(), [xe, { loading: F }] = r(), [Se, { loading: I }] = s(), [Ce, { loading: L }] = i(), [we] = te(), R = E?.clientPortalConfigs?.items ?? [], Te = E?.clientPortalConfigs?.hasMore ?? !1, z = j?.clientPortalStats, [B, V] = u(!1), [Ee, H] = u(!1), [U, W] = u(null), [G, K] = u(b), [q, J] = u(null), [Y, X] = u(null), Z = async () => {
46
+ await Promise.all([k(), M()]);
47
+ }, De = async (e) => {
34
48
  if (e.shareToken) try {
35
- await navigator.clipboard.writeText(pe(e.shareToken)), S(e.id), window.setTimeout(() => S(null), 2e3);
49
+ await navigator.clipboard.writeText(ve(e.shareToken)), C(e.id), window.setTimeout(() => C(null), 2e3);
36
50
  } catch {
37
51
  x({
38
52
  title: "Clipboard unavailable",
39
53
  variant: "error"
40
54
  });
41
55
  }
42
- }, be = async (e) => {
56
+ }, Oe = async (e) => {
43
57
  X(e.id);
44
58
  try {
45
- await A({ variables: { id: e.id } }), x({
59
+ await N({ variables: { id: e.id } }), x({
46
60
  title: "Share link regenerated",
47
61
  variant: "success"
48
- }), await E();
62
+ }), await k();
49
63
  } catch {
50
64
  x({
51
65
  title: "Failed to regenerate link",
@@ -54,35 +68,73 @@ function b() {
54
68
  } finally {
55
69
  X(null);
56
70
  }
57
- }, xe = () => {
58
- G(null), q(y), U(!0);
59
- }, Se = (e) => {
60
- G(e), q({
71
+ }, ke = () => {
72
+ W(null), K(b), H(!0);
73
+ }, Ae = (e) => {
74
+ W(e), K({
61
75
  clientName: e.clientName,
62
76
  contactEmail: e.contactEmail ?? "",
63
77
  contactPhone: e.contactPhone ?? "",
64
- description: e.description ?? ""
65
- }), U(!0);
78
+ description: e.description ?? "",
79
+ logoUrl: e.logoUrl ?? ""
80
+ }), H(!0);
81
+ }, je = async (e) => {
82
+ T(!0);
83
+ try {
84
+ let { data: t } = await S.mutate({
85
+ mutation: _e,
86
+ variables: { path: "/TimeCampus/ClientLogos" }
87
+ }), n = t?.ensureFolderByPath?.id;
88
+ if (!n) throw Error("Could not resolve upload folder");
89
+ let r = await h({
90
+ client: S,
91
+ blob: e,
92
+ filename: e.name,
93
+ mimeType: e.type || "application/octet-stream",
94
+ folderId: n
95
+ }), { data: i } = await S.query({
96
+ query: y,
97
+ variables: {
98
+ id: r.id,
99
+ expiresIn: 604800
100
+ },
101
+ fetchPolicy: "no-cache"
102
+ }), a = i?.getFileDownloadUrl;
103
+ if (!a) throw Error("No download URL returned");
104
+ K((e) => ({
105
+ ...e,
106
+ logoUrl: a
107
+ }));
108
+ } catch {
109
+ x({
110
+ title: "Failed to upload logo",
111
+ variant: "error"
112
+ });
113
+ } finally {
114
+ T(!1);
115
+ }
66
116
  }, Q = () => {
67
- U(!1), G(null);
68
- }, Ce = async () => {
69
- if (K.clientName.trim()) try {
70
- W ? (await P({ variables: {
71
- id: W.id,
117
+ H(!1), W(null);
118
+ }, Me = async () => {
119
+ if (G.clientName.trim()) try {
120
+ U ? (await Se({ variables: {
121
+ id: U.id,
72
122
  input: {
73
- clientName: K.clientName.trim(),
74
- contactEmail: K.contactEmail.trim() || void 0,
75
- contactPhone: K.contactPhone.trim() || void 0,
76
- description: K.description.trim() || void 0
123
+ clientName: G.clientName.trim(),
124
+ contactEmail: G.contactEmail.trim() || void 0,
125
+ contactPhone: G.contactPhone.trim() || void 0,
126
+ description: G.description.trim() || void 0,
127
+ logoUrl: G.logoUrl || void 0
77
128
  }
78
129
  } }), x({
79
130
  title: "Client updated",
80
131
  variant: "success"
81
- })) : (await M({ variables: { input: {
82
- clientName: K.clientName.trim(),
83
- contactEmail: K.contactEmail.trim() || void 0,
84
- contactPhone: K.contactPhone.trim() || void 0,
85
- description: K.description.trim() || void 0,
132
+ })) : (await xe({ variables: { input: {
133
+ clientName: G.clientName.trim(),
134
+ contactEmail: G.contactEmail.trim() || void 0,
135
+ contactPhone: G.contactPhone.trim() || void 0,
136
+ description: G.description.trim() || void 0,
137
+ logoUrl: G.logoUrl || void 0,
86
138
  isActive: !0
87
139
  } } }), x({
88
140
  title: "Client added",
@@ -90,26 +142,26 @@ function b() {
90
142
  })), Q(), await Z();
91
143
  } catch {
92
144
  x({
93
- title: W ? "Failed to update client" : "Failed to add client",
145
+ title: U ? "Failed to update client" : "Failed to add client",
94
146
  variant: "error"
95
147
  });
96
148
  }
97
- }, we = async () => {
98
- if (J) try {
99
- await I({ variables: { id: J.id } }), x({
149
+ }, Ne = async () => {
150
+ if (q) try {
151
+ await Ce({ variables: { id: q.id } }), x({
100
152
  title: "Client deleted",
101
153
  variant: "success"
102
- }), Y(null), await Z();
154
+ }), J(null), await Z();
103
155
  } catch {
104
156
  x({
105
157
  title: "Failed to delete client",
106
158
  variant: "error"
107
159
  });
108
160
  }
109
- }, Te = async (e) => {
161
+ }, Pe = async (e) => {
110
162
  X(e.id);
111
163
  try {
112
- await R({ variables: {
164
+ await we({ variables: {
113
165
  id: e.id,
114
166
  isActive: !e.isActive
115
167
  } }), x({
@@ -124,13 +176,13 @@ function b() {
124
176
  } finally {
125
177
  X(null);
126
178
  }
127
- }, Ee = async () => {
128
- H(!0);
179
+ }, Fe = async () => {
180
+ V(!0);
129
181
  try {
130
- await D({
182
+ await A({
131
183
  variables: {
132
- limit: _,
133
- offset: z.length
184
+ limit: g,
185
+ offset: R.length
134
186
  },
135
187
  updateQuery: (e, { fetchMoreResult: t }) => t ? {
136
188
  ...e,
@@ -141,58 +193,58 @@ function b() {
141
193
  } : e
142
194
  });
143
195
  } finally {
144
- H(!1);
196
+ V(!1);
145
197
  }
146
198
  };
147
- if (w && z.length === 0) return /* @__PURE__ */ h(a, {
199
+ if (D && R.length === 0) return /* @__PURE__ */ p(c, {
148
200
  title: "Client Portal",
149
201
  description: "Manage clients and their projects",
150
- children: /* @__PURE__ */ h(n, {
202
+ children: /* @__PURE__ */ p(n, {
151
203
  variant: "list",
152
204
  count: 4
153
205
  })
154
206
  });
155
- if (T) return /* @__PURE__ */ h(a, {
207
+ if (O) return /* @__PURE__ */ p(c, {
156
208
  title: "Client Portal",
157
209
  description: "Manage clients and their projects",
158
- children: /* @__PURE__ */ h("div", {
210
+ children: /* @__PURE__ */ p("div", {
159
211
  className: "rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text",
160
212
  children: "Failed to load client portal data. Please try again later."
161
213
  })
162
214
  });
163
- let $ = /* @__PURE__ */ g("button", {
164
- onClick: xe,
215
+ let $ = /* @__PURE__ */ m("button", {
216
+ onClick: ke,
165
217
  className: "inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
166
- children: [/* @__PURE__ */ h(le, { className: "h-4 w-4" }), "Add Client"]
218
+ children: [/* @__PURE__ */ p(fe, { className: "h-4 w-4" }), "Add Client"]
167
219
  });
168
- return /* @__PURE__ */ g(a, {
220
+ return /* @__PURE__ */ m(c, {
169
221
  title: "Client Portal",
170
222
  description: "Manage clients and their projects",
171
223
  purpose: "Give clients a transparent window into the work you do for them — the projects, hours and progress tied to each one — so billing conversations are backed by data and trust, not spreadsheets.",
172
224
  actions: $,
173
225
  children: [
174
- /* @__PURE__ */ g("div", {
226
+ /* @__PURE__ */ m("div", {
175
227
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
176
228
  children: [
177
- /* @__PURE__ */ h(t, {
229
+ /* @__PURE__ */ p(t, {
178
230
  featured: !0,
179
231
  title: "Active Clients",
180
- value: String(B?.activeClients ?? 0),
181
- icon: /* @__PURE__ */ h(c, { className: "h-5 w-5" })
232
+ value: String(z?.activeClients ?? 0),
233
+ icon: /* @__PURE__ */ p(oe, { className: "h-5 w-5" })
182
234
  }),
183
- /* @__PURE__ */ h(t, {
235
+ /* @__PURE__ */ p(t, {
184
236
  title: "Total Projects",
185
- value: String(B?.totalProjectsShared ?? 0),
186
- icon: /* @__PURE__ */ h(d, { className: "h-5 w-5" })
237
+ value: String(z?.totalProjectsShared ?? 0),
238
+ icon: /* @__PURE__ */ p(le, { className: "h-5 w-5" })
187
239
  }),
188
- /* @__PURE__ */ h(t, {
240
+ /* @__PURE__ */ p(t, {
189
241
  title: "Total Clients",
190
- value: String(B?.totalClients ?? 0),
191
- icon: /* @__PURE__ */ h(u, { className: "h-5 w-5" })
242
+ value: String(z?.totalClients ?? 0),
243
+ icon: /* @__PURE__ */ p(ce, { className: "h-5 w-5" })
192
244
  }),
193
- /* @__PURE__ */ h(t, {
245
+ /* @__PURE__ */ p(t, {
194
246
  title: "Active",
195
- value: String(B?.activeClients ?? 0),
247
+ value: String(z?.activeClients ?? 0),
196
248
  trend: {
197
249
  value: 0,
198
250
  label: "clients"
@@ -200,28 +252,28 @@ function b() {
200
252
  })
201
253
  ]
202
254
  }),
203
- z.length === 0 ? /* @__PURE__ */ h(ae, {
255
+ R.length === 0 ? /* @__PURE__ */ p(ne, {
204
256
  illustration: "empty-people",
205
257
  title: "No clients configured yet",
206
258
  description: "Add your first client to share projects and tracked hours with them.",
207
259
  action: $
208
- }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
260
+ }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("div", {
209
261
  className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop",
210
- children: /* @__PURE__ */ h("div", {
262
+ children: /* @__PURE__ */ p("div", {
211
263
  className: "divide-y divide-border",
212
- children: z.map((t) => {
213
- let n = t.projects.reduce((e, t) => e + t.trackedHours, 0), r = ve === t.id;
214
- return /* @__PURE__ */ g("div", {
264
+ children: R.map((t) => {
265
+ let n = t.projects.reduce((e, t) => e + t.trackedHours, 0), r = Y === t.id;
266
+ return /* @__PURE__ */ m("div", {
215
267
  className: "flex flex-col gap-3 p-4 hover:bg-bg-sunken/50 transition-colors sm:flex-row sm:items-center sm:justify-between",
216
- children: [/* @__PURE__ */ g("div", {
268
+ children: [/* @__PURE__ */ m("div", {
217
269
  className: "flex items-center gap-3",
218
- children: [/* @__PURE__ */ h("div", {
270
+ children: [/* @__PURE__ */ p("div", {
219
271
  className: "flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-sm font-medium text-primary",
220
272
  children: e(t.clientName)
221
- }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("p", {
273
+ }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("p", {
222
274
  className: "text-sm font-medium text-text-primary",
223
275
  children: t.clientName
224
- }), /* @__PURE__ */ g("p", {
276
+ }), /* @__PURE__ */ m("p", {
225
277
  className: "text-xs text-text-secondary",
226
278
  children: [
227
279
  t.projects.length,
@@ -229,170 +281,195 @@ function b() {
229
281
  " ",
230
282
  n,
231
283
  "h tracked",
232
- t.contactEmail && /* @__PURE__ */ g(m, { children: [" · ", t.contactEmail] })
284
+ t.contactEmail && /* @__PURE__ */ m(f, { children: [" · ", t.contactEmail] })
233
285
  ]
234
286
  })] })]
235
- }), /* @__PURE__ */ g("div", {
287
+ }), /* @__PURE__ */ m("div", {
236
288
  className: "flex flex-wrap items-center gap-2",
237
289
  children: [
238
- t.isActive && t.shareToken && /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
290
+ t.isActive && t.shareToken && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
239
291
  type: "button",
240
- onClick: () => void ye(t),
292
+ onClick: () => void De(t),
241
293
  title: "Copy the client's private share link",
242
294
  className: "inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50",
243
- children: he === t.id ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(l, { className: "h-3.5 w-3.5" }), " Copied"] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(f, { className: "h-3.5 w-3.5" }), " Share link"] })
244
- }), /* @__PURE__ */ h("button", {
295
+ children: be === t.id ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(se, { className: "h-3.5 w-3.5" }), " Copied"] }) : /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(ue, { className: "h-3.5 w-3.5" }), " Share link"] })
296
+ }), /* @__PURE__ */ p("button", {
245
297
  type: "button",
246
- disabled: r && j,
247
- onClick: () => void be(t),
298
+ disabled: r && P,
299
+ onClick: () => void Oe(t),
248
300
  title: "Regenerate the link (invalidates the old one)",
249
301
  className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50",
250
- children: /* @__PURE__ */ h(de, { className: `h-3.5 w-3.5 ${r && j ? "animate-spin" : ""}` })
302
+ children: /* @__PURE__ */ p(me, { className: `h-3.5 w-3.5 ${r && P ? "animate-spin" : ""}` })
251
303
  })] }),
252
- /* @__PURE__ */ g("button", {
304
+ /* @__PURE__ */ m("button", {
253
305
  type: "button",
254
306
  disabled: r,
255
- onClick: () => void Te(t),
307
+ onClick: () => void Pe(t),
256
308
  title: t.isActive ? "Deactivate client portal" : "Activate client portal",
257
309
  className: "inline-flex items-center gap-1 rounded-md border border-border-seam bg-bg-surface px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken/50 disabled:opacity-50",
258
- children: [/* @__PURE__ */ h(ue, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
310
+ children: [/* @__PURE__ */ p(pe, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
259
311
  }),
260
- /* @__PURE__ */ h(oe, {
312
+ /* @__PURE__ */ p(re, {
261
313
  label: t.isActive ? "active" : "inactive",
262
314
  status: t.isActive ? "active" : "inactive"
263
315
  }),
264
- /* @__PURE__ */ h("button", {
316
+ /* @__PURE__ */ p("button", {
265
317
  type: "button",
266
- onClick: () => Se(t),
318
+ onClick: () => Ae(t),
267
319
  title: "Edit client",
268
320
  className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-text-secondary hover:bg-bg-sunken/50",
269
- children: /* @__PURE__ */ h(p, { className: "h-3.5 w-3.5" })
321
+ children: /* @__PURE__ */ p(de, { className: "h-3.5 w-3.5" })
270
322
  }),
271
- /* @__PURE__ */ h("button", {
323
+ /* @__PURE__ */ p("button", {
272
324
  type: "button",
273
- onClick: () => Y(t),
325
+ onClick: () => J(t),
274
326
  title: "Delete client",
275
327
  className: "inline-flex items-center rounded-md border border-border-seam bg-bg-surface p-1.5 text-status-error-text hover:bg-status-error-bg",
276
- children: /* @__PURE__ */ h(fe, { className: "h-3.5 w-3.5" })
328
+ children: /* @__PURE__ */ p(he, { className: "h-3.5 w-3.5" })
277
329
  })
278
330
  ]
279
331
  })]
280
332
  }, t.id);
281
333
  })
282
334
  })
283
- }), ge && /* @__PURE__ */ h("div", {
335
+ }), Te && /* @__PURE__ */ p("div", {
284
336
  className: "mt-4 flex justify-center",
285
- children: /* @__PURE__ */ h("button", {
286
- onClick: Ee,
287
- disabled: V,
337
+ children: /* @__PURE__ */ p("button", {
338
+ onClick: Fe,
339
+ disabled: B,
288
340
  className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50",
289
- children: V ? "Loading..." : "Load more"
341
+ children: B ? "Loading..." : "Load more"
290
342
  })
291
343
  })] }),
292
- /* @__PURE__ */ h(o, {
293
- open: _e,
344
+ /* @__PURE__ */ p(l, {
345
+ open: Ee,
294
346
  onClose: Q,
295
- title: W ? "Edit Client" : "Add Client",
296
- description: W ? "Update this client's portal details." : "Set up a client so you can share projects and tracked hours with them.",
297
- footer: /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
347
+ title: U ? "Edit Client" : "Add Client",
348
+ description: U ? "Update this client's portal details." : "Set up a client so you can share projects and tracked hours with them.",
349
+ footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
298
350
  onClick: Q,
299
351
  className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
300
352
  children: "Cancel"
301
- }), /* @__PURE__ */ h("button", {
302
- onClick: Ce,
303
- disabled: N || F || !K.clientName.trim(),
353
+ }), /* @__PURE__ */ p("button", {
354
+ onClick: Me,
355
+ disabled: F || I || !G.clientName.trim(),
304
356
  className: "rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50",
305
- children: N || F ? "Saving..." : W ? "Save Changes" : "Add Client"
357
+ children: F || I ? "Saving..." : U ? "Save Changes" : "Add Client"
306
358
  })] }),
307
- children: /* @__PURE__ */ g("div", {
359
+ children: /* @__PURE__ */ m("div", {
308
360
  className: "space-y-4",
309
361
  children: [
310
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
362
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
311
363
  className: "block text-sm font-medium text-text-primary",
312
364
  children: "Client Name"
313
- }), /* @__PURE__ */ h("input", {
365
+ }), /* @__PURE__ */ p("input", {
314
366
  type: "text",
315
367
  placeholder: "e.g. Acme Corp",
316
- value: K.clientName,
317
- onChange: (e) => q({
318
- ...K,
368
+ value: G.clientName,
369
+ onChange: (e) => K({
370
+ ...G,
319
371
  clientName: e.target.value
320
372
  }),
321
- className: v
373
+ className: _
322
374
  })] }),
323
- /* @__PURE__ */ g("div", {
375
+ /* @__PURE__ */ m("div", {
324
376
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
325
- children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
377
+ children: [/* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
326
378
  className: "block text-sm font-medium text-text-primary",
327
379
  children: "Contact Email"
328
- }), /* @__PURE__ */ h("input", {
380
+ }), /* @__PURE__ */ p("input", {
329
381
  type: "email",
330
382
  placeholder: "contact@client.com",
331
- value: K.contactEmail,
332
- onChange: (e) => q({
333
- ...K,
383
+ value: G.contactEmail,
384
+ onChange: (e) => K({
385
+ ...G,
334
386
  contactEmail: e.target.value
335
387
  }),
336
- className: v
337
- })] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
388
+ className: _
389
+ })] }), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
338
390
  className: "block text-sm font-medium text-text-primary",
339
391
  children: "Contact Phone"
340
- }), /* @__PURE__ */ h("input", {
392
+ }), /* @__PURE__ */ p("input", {
341
393
  type: "tel",
342
394
  placeholder: "Optional",
343
- value: K.contactPhone,
344
- onChange: (e) => q({
345
- ...K,
395
+ value: G.contactPhone,
396
+ onChange: (e) => K({
397
+ ...G,
346
398
  contactPhone: e.target.value
347
399
  }),
348
- className: v
400
+ className: _
349
401
  })] })]
350
402
  }),
351
- /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
403
+ /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("label", {
352
404
  className: "block text-sm font-medium text-text-primary",
353
405
  children: "Description"
354
- }), /* @__PURE__ */ h("textarea", {
406
+ }), /* @__PURE__ */ p("textarea", {
355
407
  rows: 3,
356
408
  placeholder: "Notes about this client (optional)",
357
- value: K.description,
358
- onChange: (e) => q({
359
- ...K,
409
+ value: G.description,
410
+ onChange: (e) => K({
411
+ ...G,
360
412
  description: e.target.value
361
413
  }),
362
- className: v
363
- })] })
414
+ className: _
415
+ })] }),
416
+ /* @__PURE__ */ m("div", { children: [
417
+ /* @__PURE__ */ p("label", {
418
+ className: "block text-sm font-medium text-text-primary",
419
+ children: "Logo"
420
+ }),
421
+ /* @__PURE__ */ p("input", {
422
+ type: "file",
423
+ accept: "image/*",
424
+ disabled: w,
425
+ onChange: (e) => {
426
+ let t = e.target.files?.[0];
427
+ t && je(t), e.target.value = "";
428
+ },
429
+ className: v
430
+ }),
431
+ w && /* @__PURE__ */ p("p", {
432
+ className: "mt-1 text-xs text-text-secondary",
433
+ children: "Uploading…"
434
+ }),
435
+ G.logoUrl && /* @__PURE__ */ p("img", {
436
+ src: G.logoUrl,
437
+ alt: "Client logo preview",
438
+ className: "mt-2 h-16 w-16 rounded-md border border-border-seam object-cover"
439
+ })
440
+ ] })
364
441
  ]
365
442
  })
366
443
  }),
367
- /* @__PURE__ */ h(o, {
368
- open: J !== null,
369
- onClose: () => Y(null),
444
+ /* @__PURE__ */ p(l, {
445
+ open: q !== null,
446
+ onClose: () => J(null),
370
447
  title: "Delete client?",
371
- description: J ? `"${J.clientName}" and its share link will be removed.` : void 0,
448
+ description: q ? `"${q.clientName}" and its share link will be removed.` : void 0,
372
449
  size: "sm",
373
- footer: /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("button", {
374
- onClick: () => Y(null),
450
+ footer: /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
451
+ onClick: () => J(null),
375
452
  className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
376
453
  children: "Cancel"
377
- }), /* @__PURE__ */ h("button", {
378
- onClick: we,
454
+ }), /* @__PURE__ */ p("button", {
455
+ onClick: Ne,
379
456
  disabled: L,
380
457
  className: "rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/80 disabled:opacity-50",
381
458
  children: L ? "Deleting..." : "Delete"
382
459
  })] }),
383
- children: /* @__PURE__ */ h("p", {
460
+ children: /* @__PURE__ */ p("p", {
384
461
  className: "text-sm text-text-secondary",
385
462
  children: "The client will lose access to their shared portal immediately. This cannot be undone."
386
463
  })
387
464
  }),
388
- /* @__PURE__ */ h(se, {
389
- toasts: b,
390
- onDismiss: me
465
+ /* @__PURE__ */ p(ie, {
466
+ toasts: d,
467
+ onDismiss: ye
391
468
  })
392
469
  ]
393
470
  });
394
471
  }
395
472
  //#endregion
396
- export { b as ClientPortalPage };
473
+ export { x as ClientPortalPage };
397
474
 
398
475
  //# sourceMappingURL=ClientPortalPage.js.map