@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.
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +236 -159
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +275 -161
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js +138 -75
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +120 -96
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
5
|
-
import { PageLayout as
|
|
6
|
-
import { EmptyState as
|
|
7
|
-
import { Modal as
|
|
8
|
-
import { StatusBadge as
|
|
9
|
-
import { ToastContainer as
|
|
10
|
-
import { useState as
|
|
11
|
-
import { Building2 as
|
|
12
|
-
import {
|
|
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
|
|
15
|
-
|
|
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
|
|
31
|
+
var b = {
|
|
19
32
|
clientName: "",
|
|
20
33
|
contactEmail: "",
|
|
21
34
|
contactPhone: "",
|
|
22
|
-
description: ""
|
|
35
|
+
description: "",
|
|
36
|
+
logoUrl: ""
|
|
23
37
|
};
|
|
24
|
-
function
|
|
25
|
-
let { toasts:
|
|
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:
|
|
32
|
-
await Promise.all([
|
|
33
|
-
},
|
|
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(
|
|
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
|
-
},
|
|
56
|
+
}, Oe = async (e) => {
|
|
43
57
|
X(e.id);
|
|
44
58
|
try {
|
|
45
|
-
await
|
|
59
|
+
await N({ variables: { id: e.id } }), x({
|
|
46
60
|
title: "Share link regenerated",
|
|
47
61
|
variant: "success"
|
|
48
|
-
}), await
|
|
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
|
-
},
|
|
58
|
-
|
|
59
|
-
},
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
},
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
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:
|
|
74
|
-
contactEmail:
|
|
75
|
-
contactPhone:
|
|
76
|
-
description:
|
|
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
|
|
82
|
-
clientName:
|
|
83
|
-
contactEmail:
|
|
84
|
-
contactPhone:
|
|
85
|
-
description:
|
|
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:
|
|
145
|
+
title: U ? "Failed to update client" : "Failed to add client",
|
|
94
146
|
variant: "error"
|
|
95
147
|
});
|
|
96
148
|
}
|
|
97
|
-
},
|
|
98
|
-
if (
|
|
99
|
-
await
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
161
|
+
}, Pe = async (e) => {
|
|
110
162
|
X(e.id);
|
|
111
163
|
try {
|
|
112
|
-
await
|
|
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
|
-
},
|
|
128
|
-
|
|
179
|
+
}, Fe = async () => {
|
|
180
|
+
V(!0);
|
|
129
181
|
try {
|
|
130
|
-
await
|
|
182
|
+
await A({
|
|
131
183
|
variables: {
|
|
132
|
-
limit:
|
|
133
|
-
offset:
|
|
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
|
-
|
|
196
|
+
V(!1);
|
|
145
197
|
}
|
|
146
198
|
};
|
|
147
|
-
if (
|
|
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__ */
|
|
202
|
+
children: /* @__PURE__ */ p(n, {
|
|
151
203
|
variant: "list",
|
|
152
204
|
count: 4
|
|
153
205
|
})
|
|
154
206
|
});
|
|
155
|
-
if (
|
|
207
|
+
if (O) return /* @__PURE__ */ p(c, {
|
|
156
208
|
title: "Client Portal",
|
|
157
209
|
description: "Manage clients and their projects",
|
|
158
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
164
|
-
onClick:
|
|
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__ */
|
|
218
|
+
children: [/* @__PURE__ */ p(fe, { className: "h-4 w-4" }), "Add Client"]
|
|
167
219
|
});
|
|
168
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
229
|
+
/* @__PURE__ */ p(t, {
|
|
178
230
|
featured: !0,
|
|
179
231
|
title: "Active Clients",
|
|
180
|
-
value: String(
|
|
181
|
-
icon: /* @__PURE__ */
|
|
232
|
+
value: String(z?.activeClients ?? 0),
|
|
233
|
+
icon: /* @__PURE__ */ p(oe, { className: "h-5 w-5" })
|
|
182
234
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
235
|
+
/* @__PURE__ */ p(t, {
|
|
184
236
|
title: "Total Projects",
|
|
185
|
-
value: String(
|
|
186
|
-
icon: /* @__PURE__ */
|
|
237
|
+
value: String(z?.totalProjectsShared ?? 0),
|
|
238
|
+
icon: /* @__PURE__ */ p(le, { className: "h-5 w-5" })
|
|
187
239
|
}),
|
|
188
|
-
/* @__PURE__ */
|
|
240
|
+
/* @__PURE__ */ p(t, {
|
|
189
241
|
title: "Total Clients",
|
|
190
|
-
value: String(
|
|
191
|
-
icon: /* @__PURE__ */
|
|
242
|
+
value: String(z?.totalClients ?? 0),
|
|
243
|
+
icon: /* @__PURE__ */ p(ce, { className: "h-5 w-5" })
|
|
192
244
|
}),
|
|
193
|
-
/* @__PURE__ */
|
|
245
|
+
/* @__PURE__ */ p(t, {
|
|
194
246
|
title: "Active",
|
|
195
|
-
value: String(
|
|
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
|
-
|
|
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__ */
|
|
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__ */
|
|
262
|
+
children: /* @__PURE__ */ p("div", {
|
|
211
263
|
className: "divide-y divide-border",
|
|
212
|
-
children:
|
|
213
|
-
let n = t.projects.reduce((e, t) => e + t.trackedHours, 0), r =
|
|
214
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
268
|
+
children: [/* @__PURE__ */ m("div", {
|
|
217
269
|
className: "flex items-center gap-3",
|
|
218
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
284
|
+
t.contactEmail && /* @__PURE__ */ m(f, { children: [" · ", t.contactEmail] })
|
|
233
285
|
]
|
|
234
286
|
})] })]
|
|
235
|
-
}), /* @__PURE__ */
|
|
287
|
+
}), /* @__PURE__ */ m("div", {
|
|
236
288
|
className: "flex flex-wrap items-center gap-2",
|
|
237
289
|
children: [
|
|
238
|
-
t.isActive && t.shareToken && /* @__PURE__ */
|
|
290
|
+
t.isActive && t.shareToken && /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p("button", {
|
|
239
291
|
type: "button",
|
|
240
|
-
onClick: () => void
|
|
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:
|
|
244
|
-
}), /* @__PURE__ */
|
|
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 &&
|
|
247
|
-
onClick: () => void
|
|
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__ */
|
|
302
|
+
children: /* @__PURE__ */ p(me, { className: `h-3.5 w-3.5 ${r && P ? "animate-spin" : ""}` })
|
|
251
303
|
})] }),
|
|
252
|
-
/* @__PURE__ */
|
|
304
|
+
/* @__PURE__ */ m("button", {
|
|
253
305
|
type: "button",
|
|
254
306
|
disabled: r,
|
|
255
|
-
onClick: () => void
|
|
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__ */
|
|
310
|
+
children: [/* @__PURE__ */ p(pe, { className: "h-3.5 w-3.5" }), t.isActive ? "Deactivate" : "Activate"]
|
|
259
311
|
}),
|
|
260
|
-
/* @__PURE__ */
|
|
312
|
+
/* @__PURE__ */ p(re, {
|
|
261
313
|
label: t.isActive ? "active" : "inactive",
|
|
262
314
|
status: t.isActive ? "active" : "inactive"
|
|
263
315
|
}),
|
|
264
|
-
/* @__PURE__ */
|
|
316
|
+
/* @__PURE__ */ p("button", {
|
|
265
317
|
type: "button",
|
|
266
|
-
onClick: () =>
|
|
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__ */
|
|
321
|
+
children: /* @__PURE__ */ p(de, { className: "h-3.5 w-3.5" })
|
|
270
322
|
}),
|
|
271
|
-
/* @__PURE__ */
|
|
323
|
+
/* @__PURE__ */ p("button", {
|
|
272
324
|
type: "button",
|
|
273
|
-
onClick: () =>
|
|
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__ */
|
|
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
|
-
}),
|
|
335
|
+
}), Te && /* @__PURE__ */ p("div", {
|
|
284
336
|
className: "mt-4 flex justify-center",
|
|
285
|
-
children: /* @__PURE__ */
|
|
286
|
-
onClick:
|
|
287
|
-
disabled:
|
|
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:
|
|
341
|
+
children: B ? "Loading..." : "Load more"
|
|
290
342
|
})
|
|
291
343
|
})] }),
|
|
292
|
-
/* @__PURE__ */
|
|
293
|
-
open:
|
|
344
|
+
/* @__PURE__ */ p(l, {
|
|
345
|
+
open: Ee,
|
|
294
346
|
onClose: Q,
|
|
295
|
-
title:
|
|
296
|
-
description:
|
|
297
|
-
footer: /* @__PURE__ */
|
|
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__ */
|
|
302
|
-
onClick:
|
|
303
|
-
disabled:
|
|
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:
|
|
357
|
+
children: F || I ? "Saving..." : U ? "Save Changes" : "Add Client"
|
|
306
358
|
})] }),
|
|
307
|
-
children: /* @__PURE__ */
|
|
359
|
+
children: /* @__PURE__ */ m("div", {
|
|
308
360
|
className: "space-y-4",
|
|
309
361
|
children: [
|
|
310
|
-
/* @__PURE__ */
|
|
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__ */
|
|
365
|
+
}), /* @__PURE__ */ p("input", {
|
|
314
366
|
type: "text",
|
|
315
367
|
placeholder: "e.g. Acme Corp",
|
|
316
|
-
value:
|
|
317
|
-
onChange: (e) =>
|
|
318
|
-
...
|
|
368
|
+
value: G.clientName,
|
|
369
|
+
onChange: (e) => K({
|
|
370
|
+
...G,
|
|
319
371
|
clientName: e.target.value
|
|
320
372
|
}),
|
|
321
|
-
className:
|
|
373
|
+
className: _
|
|
322
374
|
})] }),
|
|
323
|
-
/* @__PURE__ */
|
|
375
|
+
/* @__PURE__ */ m("div", {
|
|
324
376
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
|
|
325
|
-
children: [/* @__PURE__ */
|
|
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__ */
|
|
380
|
+
}), /* @__PURE__ */ p("input", {
|
|
329
381
|
type: "email",
|
|
330
382
|
placeholder: "contact@client.com",
|
|
331
|
-
value:
|
|
332
|
-
onChange: (e) =>
|
|
333
|
-
...
|
|
383
|
+
value: G.contactEmail,
|
|
384
|
+
onChange: (e) => K({
|
|
385
|
+
...G,
|
|
334
386
|
contactEmail: e.target.value
|
|
335
387
|
}),
|
|
336
|
-
className:
|
|
337
|
-
})] }), /* @__PURE__ */
|
|
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__ */
|
|
392
|
+
}), /* @__PURE__ */ p("input", {
|
|
341
393
|
type: "tel",
|
|
342
394
|
placeholder: "Optional",
|
|
343
|
-
value:
|
|
344
|
-
onChange: (e) =>
|
|
345
|
-
...
|
|
395
|
+
value: G.contactPhone,
|
|
396
|
+
onChange: (e) => K({
|
|
397
|
+
...G,
|
|
346
398
|
contactPhone: e.target.value
|
|
347
399
|
}),
|
|
348
|
-
className:
|
|
400
|
+
className: _
|
|
349
401
|
})] })]
|
|
350
402
|
}),
|
|
351
|
-
/* @__PURE__ */
|
|
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__ */
|
|
406
|
+
}), /* @__PURE__ */ p("textarea", {
|
|
355
407
|
rows: 3,
|
|
356
408
|
placeholder: "Notes about this client (optional)",
|
|
357
|
-
value:
|
|
358
|
-
onChange: (e) =>
|
|
359
|
-
...
|
|
409
|
+
value: G.description,
|
|
410
|
+
onChange: (e) => K({
|
|
411
|
+
...G,
|
|
360
412
|
description: e.target.value
|
|
361
413
|
}),
|
|
362
|
-
className:
|
|
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__ */
|
|
368
|
-
open:
|
|
369
|
-
onClose: () =>
|
|
444
|
+
/* @__PURE__ */ p(l, {
|
|
445
|
+
open: q !== null,
|
|
446
|
+
onClose: () => J(null),
|
|
370
447
|
title: "Delete client?",
|
|
371
|
-
description:
|
|
448
|
+
description: q ? `"${q.clientName}" and its share link will be removed.` : void 0,
|
|
372
449
|
size: "sm",
|
|
373
|
-
footer: /* @__PURE__ */
|
|
374
|
-
onClick: () =>
|
|
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__ */
|
|
378
|
-
onClick:
|
|
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__ */
|
|
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__ */
|
|
389
|
-
toasts:
|
|
390
|
-
onDismiss:
|
|
465
|
+
/* @__PURE__ */ p(ie, {
|
|
466
|
+
toasts: d,
|
|
467
|
+
onDismiss: ye
|
|
391
468
|
})
|
|
392
469
|
]
|
|
393
470
|
});
|
|
394
471
|
}
|
|
395
472
|
//#endregion
|
|
396
|
-
export {
|
|
473
|
+
export { x as ClientPortalPage };
|
|
397
474
|
|
|
398
475
|
//# sourceMappingURL=ClientPortalPage.js.map
|