@burdenoff/microfe-crewfoundry 2026.827.1 → 2026.830.1
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/crewfoundry/CrewFoundryRoot.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoot.js +190 -185
- package/dist/crewfoundry/CrewFoundryRoot.js.map +1 -1
- package/dist/crewfoundry/components/forms/ConfirmDialog.d.ts.map +1 -1
- package/dist/crewfoundry/components/forms/ConfirmDialog.js +25 -24
- package/dist/crewfoundry/components/forms/ConfirmDialog.js.map +1 -1
- package/dist/crewfoundry/components/forms/FormModal.d.ts.map +1 -1
- package/dist/crewfoundry/components/forms/FormModal.js +103 -101
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +207 -210
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +75 -78
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +101 -104
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +95 -98
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +101 -104
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/dist/shared/hooks/useTr.d.ts +17 -0
- package/dist/shared/hooks/useTr.d.ts.map +1 -0
- package/dist/shared/hooks/useTr.js +14 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { useTr as t } from "../../shared/hooks/useTr.js";
|
|
3
|
+
import { getInitials as n } from "../components/viz/lib/format.js";
|
|
4
|
+
import { RatingStars as r } from "../components/viz/RatingStars.js";
|
|
5
|
+
import { SegmentedTabs as i } from "../components/viz/SegmentedTabs.js";
|
|
5
6
|
import "../components/viz/index.js";
|
|
6
|
-
import { useEffect as
|
|
7
|
-
import { CheckCircle2 as
|
|
8
|
-
import { useNavigate as
|
|
9
|
-
import { jsx as
|
|
10
|
-
import { Button as
|
|
11
|
-
import { usePagination as
|
|
12
|
-
import { useI18n as O } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
7
|
+
import { useEffect as a, useMemo as o, useState as s } from "react";
|
|
8
|
+
import { CheckCircle2 as c, Clock3 as l, PauseCircle as u, Search as d, ShoppingBag as f, Star as p, Users as m } from "lucide-react";
|
|
9
|
+
import { useNavigate as h } from "react-router";
|
|
10
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
11
|
+
import { Button as v, CardContent as y, CardHeader as b, CardTitle as x, GlassCard as S, GradientText as C, IllustratedEmptyState as w, Input as T, PagePurpose as E, Pagination as D } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
import { usePagination as O } from "@burdenoff/fe-libs/shared/hooks";
|
|
13
13
|
//#region src/crewfoundry/pages/MarketplaceMarketplacePagePage.tsx
|
|
14
14
|
function k(e) {
|
|
15
15
|
switch (e) {
|
|
16
16
|
case "active": return {
|
|
17
17
|
label: "Available",
|
|
18
|
-
icon:
|
|
18
|
+
icon: c,
|
|
19
19
|
className: "bg-status-success-bg text-status-success-text"
|
|
20
20
|
};
|
|
21
21
|
case "pending": return {
|
|
22
22
|
label: "Pending",
|
|
23
|
-
icon:
|
|
23
|
+
icon: l,
|
|
24
24
|
className: "bg-status-warning-bg text-status-warning-text"
|
|
25
25
|
};
|
|
26
26
|
default: return {
|
|
27
27
|
label: "Unavailable",
|
|
28
|
-
icon:
|
|
28
|
+
icon: u,
|
|
29
29
|
className: "bg-bg-sunken text-text-secondary"
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
function A() {
|
|
34
|
-
let
|
|
35
|
-
let n = s(e);
|
|
36
|
-
return n === e ? t : n;
|
|
37
|
-
}, { page: M, pageSize: N, offset: P, setPage: F } = D({ initialPageSize: 9 }), [I, L] = o(""), [R, z] = o("all"), B = a(() => {
|
|
34
|
+
let c = t(), u = h(), { marketplaceServices: A } = e(), { page: j, pageSize: M, offset: N, setPage: P } = O({ initialPageSize: 9 }), [F, I] = s(""), [L, R] = s("all"), z = o(() => {
|
|
38
35
|
let e = /* @__PURE__ */ new Map();
|
|
39
36
|
for (let t of A) e.set(t.category, (e.get(t.category) ?? 0) + 1);
|
|
40
37
|
let t = [{
|
|
@@ -48,209 +45,209 @@ function A() {
|
|
|
48
45
|
count: r
|
|
49
46
|
});
|
|
50
47
|
return t;
|
|
51
|
-
}, [A]),
|
|
52
|
-
let e =
|
|
53
|
-
return A.filter((t) =>
|
|
48
|
+
}, [A]), B = o(() => {
|
|
49
|
+
let e = F.trim().toLowerCase();
|
|
50
|
+
return A.filter((t) => L !== "all" && t.category !== L ? !1 : e ? `${t.title} ${t.provider} ${t.category} ${t.skills.join(" ")}`.toLowerCase().includes(e) : !0);
|
|
54
51
|
}, [
|
|
55
52
|
A,
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
F,
|
|
54
|
+
L
|
|
58
55
|
]);
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
a(() => {
|
|
57
|
+
P(1);
|
|
61
58
|
}, [
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
F,
|
|
60
|
+
L,
|
|
61
|
+
P
|
|
65
62
|
]);
|
|
66
|
-
let
|
|
67
|
-
return /* @__PURE__ */
|
|
63
|
+
let V = A.length > 0 ? (A.reduce((e, t) => e + t.rating, 0) / A.length).toFixed(1) : "0.0", H = A.reduce((e, t) => e + t.reviews, 0), U = B.slice(N, N + M), W = (e) => u(`../marketplace/service-details-page?id=${e}`);
|
|
64
|
+
return /* @__PURE__ */ _("div", {
|
|
68
65
|
className: "space-y-6 p-6",
|
|
69
66
|
children: [
|
|
70
|
-
/* @__PURE__ */
|
|
71
|
-
/* @__PURE__ */
|
|
67
|
+
/* @__PURE__ */ _("div", { children: [
|
|
68
|
+
/* @__PURE__ */ g("h1", {
|
|
72
69
|
className: "text-2xl font-semibold",
|
|
73
|
-
children: /* @__PURE__ */
|
|
70
|
+
children: /* @__PURE__ */ g(C, { children: c("crewfoundry.marketplace.title", "Talent Marketplace") })
|
|
74
71
|
}),
|
|
75
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ g("p", {
|
|
76
73
|
className: "text-sm text-text-secondary",
|
|
77
|
-
children:
|
|
74
|
+
children: c("crewfoundry.marketplace.subtitle", "Book vetted specialists and services")
|
|
78
75
|
}),
|
|
79
|
-
/* @__PURE__ */
|
|
76
|
+
/* @__PURE__ */ g(E, {
|
|
80
77
|
className: "mt-1",
|
|
81
78
|
children: "Discover freelancers and services for your projects — search, filter by category, and hire the right talent on demand."
|
|
82
79
|
})
|
|
83
80
|
] }),
|
|
84
|
-
/* @__PURE__ */
|
|
81
|
+
/* @__PURE__ */ _("div", {
|
|
85
82
|
className: "grid gap-4 md:grid-cols-3",
|
|
86
83
|
children: [
|
|
87
|
-
/* @__PURE__ */
|
|
84
|
+
/* @__PURE__ */ _(S, {
|
|
88
85
|
treatment: "glass",
|
|
89
86
|
glow: !0,
|
|
90
87
|
className: "border-border-seam",
|
|
91
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ _(b, {
|
|
92
89
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
93
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ g(x, {
|
|
94
91
|
className: "text-sm font-medium",
|
|
95
|
-
children:
|
|
96
|
-
}), /* @__PURE__ */
|
|
97
|
-
}), /* @__PURE__ */
|
|
92
|
+
children: c("crewfoundry.marketplace.stats.services", "Services")
|
|
93
|
+
}), /* @__PURE__ */ g(f, { className: "h-4 w-4 text-text-secondary" })]
|
|
94
|
+
}), /* @__PURE__ */ g(y, { children: /* @__PURE__ */ g("div", {
|
|
98
95
|
className: "text-2xl font-bold tabular-nums",
|
|
99
96
|
children: A.length
|
|
100
97
|
}) })]
|
|
101
98
|
}),
|
|
102
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ _(S, {
|
|
103
100
|
treatment: "metric",
|
|
104
101
|
className: "border-border-seam",
|
|
105
|
-
children: [/* @__PURE__ */
|
|
102
|
+
children: [/* @__PURE__ */ _(b, {
|
|
106
103
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ g(x, {
|
|
108
105
|
className: "text-sm font-medium",
|
|
109
|
-
children:
|
|
110
|
-
}), /* @__PURE__ */
|
|
111
|
-
}), /* @__PURE__ */
|
|
106
|
+
children: c("crewfoundry.marketplace.stats.avgRating", "Avg Rating")
|
|
107
|
+
}), /* @__PURE__ */ g(p, { className: "h-4 w-4 text-status-warning-text" })]
|
|
108
|
+
}), /* @__PURE__ */ g(y, { children: /* @__PURE__ */ g("div", {
|
|
112
109
|
className: "text-2xl font-bold tabular-nums",
|
|
113
|
-
children:
|
|
110
|
+
children: V
|
|
114
111
|
}) })]
|
|
115
112
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ _(S, {
|
|
117
114
|
treatment: "metric",
|
|
118
115
|
className: "border-border-seam",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ _(b, {
|
|
120
117
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
121
|
-
children: [/* @__PURE__ */
|
|
118
|
+
children: [/* @__PURE__ */ g(x, {
|
|
122
119
|
className: "text-sm font-medium",
|
|
123
|
-
children:
|
|
124
|
-
}), /* @__PURE__ */
|
|
125
|
-
}), /* @__PURE__ */
|
|
120
|
+
children: c("crewfoundry.marketplace.stats.totalReviews", "Total Reviews")
|
|
121
|
+
}), /* @__PURE__ */ g(m, { className: "h-4 w-4 text-text-secondary" })]
|
|
122
|
+
}), /* @__PURE__ */ g(y, { children: /* @__PURE__ */ g("div", {
|
|
126
123
|
className: "text-2xl font-bold tabular-nums",
|
|
127
|
-
children:
|
|
124
|
+
children: H
|
|
128
125
|
}) })]
|
|
129
126
|
})
|
|
130
127
|
]
|
|
131
128
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ _("div", {
|
|
133
130
|
className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",
|
|
134
|
-
children: [/* @__PURE__ */
|
|
131
|
+
children: [/* @__PURE__ */ _("div", {
|
|
135
132
|
className: "relative w-full max-w-sm",
|
|
136
|
-
children: [/* @__PURE__ */
|
|
133
|
+
children: [/* @__PURE__ */ g(d, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ g(T, {
|
|
137
134
|
placeholder: "Search services, providers, skills…",
|
|
138
135
|
className: "pl-9",
|
|
139
|
-
value:
|
|
140
|
-
onChange: (e) =>
|
|
136
|
+
value: F,
|
|
137
|
+
onChange: (e) => I(e.target.value),
|
|
141
138
|
"aria-label": "Search marketplace services"
|
|
142
139
|
})]
|
|
143
|
-
}), /* @__PURE__ */
|
|
140
|
+
}), /* @__PURE__ */ g("div", {
|
|
144
141
|
className: "-mx-1 overflow-x-auto px-1 pb-1",
|
|
145
|
-
children: /* @__PURE__ */
|
|
146
|
-
tabs:
|
|
147
|
-
value:
|
|
148
|
-
onChange:
|
|
142
|
+
children: /* @__PURE__ */ g(i, {
|
|
143
|
+
tabs: z,
|
|
144
|
+
value: L,
|
|
145
|
+
onChange: R,
|
|
149
146
|
"aria-label": "Filter services by category"
|
|
150
147
|
})
|
|
151
148
|
})]
|
|
152
149
|
}),
|
|
153
|
-
|
|
150
|
+
U.length === 0 ? /* @__PURE__ */ g(w, {
|
|
154
151
|
illustration: "empty-search",
|
|
155
152
|
title: "No services found",
|
|
156
153
|
description: "Try a different search term or category to find the help you need."
|
|
157
|
-
}) : /* @__PURE__ */
|
|
154
|
+
}) : /* @__PURE__ */ g("div", {
|
|
158
155
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
159
|
-
children:
|
|
160
|
-
let
|
|
161
|
-
return /* @__PURE__ */
|
|
156
|
+
children: U.map((e) => {
|
|
157
|
+
let t = k(e.status), i = t.icon;
|
|
158
|
+
return /* @__PURE__ */ g(S, {
|
|
162
159
|
treatment: "glass",
|
|
163
160
|
role: "button",
|
|
164
161
|
tabIndex: 0,
|
|
165
162
|
"aria-label": `View and book ${e.title} by ${e.provider}`,
|
|
166
|
-
onClick: () =>
|
|
163
|
+
onClick: () => W(e.id),
|
|
167
164
|
onKeyDown: (t) => {
|
|
168
|
-
(t.key === "Enter" || t.key === " ") && (t.preventDefault(),
|
|
165
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), W(e.id));
|
|
169
166
|
},
|
|
170
167
|
className: "cursor-pointer border-border-seam transition-shadow hover:shadow-elevation-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
171
|
-
children: /* @__PURE__ */
|
|
168
|
+
children: /* @__PURE__ */ _(y, {
|
|
172
169
|
className: "flex h-full flex-col gap-3 p-4",
|
|
173
170
|
children: [
|
|
174
|
-
/* @__PURE__ */
|
|
171
|
+
/* @__PURE__ */ _("div", {
|
|
175
172
|
className: "flex items-start justify-between gap-2",
|
|
176
|
-
children: [/* @__PURE__ */
|
|
173
|
+
children: [/* @__PURE__ */ _("div", {
|
|
177
174
|
className: "flex items-center gap-2.5",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ g("span", {
|
|
179
176
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-action-primary-bg text-sm font-semibold text-action-primary-text",
|
|
180
177
|
"aria-hidden": "true",
|
|
181
|
-
children:
|
|
182
|
-
}), /* @__PURE__ */
|
|
178
|
+
children: n(e.provider)
|
|
179
|
+
}), /* @__PURE__ */ _("div", {
|
|
183
180
|
className: "min-w-0",
|
|
184
|
-
children: [/* @__PURE__ */
|
|
181
|
+
children: [/* @__PURE__ */ g("p", {
|
|
185
182
|
className: "truncate text-sm font-semibold",
|
|
186
183
|
children: e.provider
|
|
187
|
-
}), /* @__PURE__ */
|
|
184
|
+
}), /* @__PURE__ */ g("span", {
|
|
188
185
|
className: "inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary",
|
|
189
186
|
children: e.category
|
|
190
187
|
})]
|
|
191
188
|
})]
|
|
192
|
-
}), /* @__PURE__ */
|
|
189
|
+
}), /* @__PURE__ */ _("div", {
|
|
193
190
|
className: "shrink-0 text-right",
|
|
194
|
-
children: [/* @__PURE__ */
|
|
191
|
+
children: [/* @__PURE__ */ _("p", {
|
|
195
192
|
className: "text-base font-bold tabular-nums",
|
|
196
193
|
children: ["$", e.price.toLocaleString()]
|
|
197
|
-
}), /* @__PURE__ */
|
|
194
|
+
}), /* @__PURE__ */ _("p", {
|
|
198
195
|
className: "text-[10px] text-text-secondary",
|
|
199
196
|
children: ["/", e.priceType]
|
|
200
197
|
})]
|
|
201
198
|
})]
|
|
202
199
|
}),
|
|
203
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ g("p", {
|
|
204
201
|
className: "text-sm font-medium",
|
|
205
202
|
children: e.title
|
|
206
203
|
}),
|
|
207
|
-
/* @__PURE__ */
|
|
204
|
+
/* @__PURE__ */ _("div", {
|
|
208
205
|
className: "flex flex-wrap items-center gap-2",
|
|
209
|
-
children: [/* @__PURE__ */
|
|
206
|
+
children: [/* @__PURE__ */ g(r, {
|
|
210
207
|
value: e.rating,
|
|
211
208
|
count: e.reviews,
|
|
212
209
|
showValue: !0,
|
|
213
210
|
size: 14
|
|
214
|
-
}), /* @__PURE__ */
|
|
215
|
-
className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${
|
|
216
|
-
children: [/* @__PURE__ */
|
|
211
|
+
}), /* @__PURE__ */ _("span", {
|
|
212
|
+
className: `inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${t.className}`,
|
|
213
|
+
children: [/* @__PURE__ */ g(i, { className: "h-3 w-3" }), t.label]
|
|
217
214
|
})]
|
|
218
215
|
}),
|
|
219
|
-
/* @__PURE__ */
|
|
216
|
+
/* @__PURE__ */ g("p", {
|
|
220
217
|
className: "line-clamp-2 text-xs text-text-secondary",
|
|
221
218
|
children: e.description
|
|
222
219
|
}),
|
|
223
|
-
/* @__PURE__ */
|
|
220
|
+
/* @__PURE__ */ g("div", {
|
|
224
221
|
className: "flex flex-wrap gap-1",
|
|
225
|
-
children: e.skills.slice(0, 4).map((e) => /* @__PURE__ */
|
|
222
|
+
children: e.skills.slice(0, 4).map((e) => /* @__PURE__ */ g("span", {
|
|
226
223
|
className: "rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary",
|
|
227
224
|
children: e
|
|
228
225
|
}, e))
|
|
229
226
|
}),
|
|
230
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ g("div", {
|
|
231
228
|
className: "mt-auto flex items-center justify-between pt-1 text-[11px] text-text-secondary",
|
|
232
|
-
children: /* @__PURE__ */
|
|
229
|
+
children: /* @__PURE__ */ _("span", {
|
|
233
230
|
className: "flex items-center gap-1",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
231
|
+
children: [/* @__PURE__ */ g(l, { className: "h-3 w-3" }), e.deliveryTime]
|
|
235
232
|
})
|
|
236
233
|
}),
|
|
237
|
-
/* @__PURE__ */
|
|
234
|
+
/* @__PURE__ */ g(v, {
|
|
238
235
|
className: "w-full",
|
|
239
236
|
onClick: (t) => {
|
|
240
|
-
t.stopPropagation(),
|
|
237
|
+
t.stopPropagation(), W(e.id);
|
|
241
238
|
},
|
|
242
|
-
children:
|
|
239
|
+
children: c("crewfoundry.marketplace.card.book", "View & Book")
|
|
243
240
|
})
|
|
244
241
|
]
|
|
245
242
|
})
|
|
246
243
|
}, e.id);
|
|
247
244
|
})
|
|
248
245
|
}),
|
|
249
|
-
|
|
250
|
-
page:
|
|
251
|
-
pageSize:
|
|
252
|
-
totalItems:
|
|
253
|
-
onPageChange:
|
|
246
|
+
B.length > M ? /* @__PURE__ */ g(D, {
|
|
247
|
+
page: j,
|
|
248
|
+
pageSize: M,
|
|
249
|
+
totalItems: B.length,
|
|
250
|
+
onPageChange: P
|
|
254
251
|
}) : null
|
|
255
252
|
]
|
|
256
253
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarketplaceMarketplacePagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/MarketplaceMarketplacePagePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport {\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle2, Clock3, PauseCircle, Search, ShoppingBag, Star, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type MarketplaceService } from '../store/crewfoundryStore';\nimport { RatingStars, SegmentedTabs, getInitials, type SegmentedTab } from '../components/viz';\n\n/** Honest availability derived from the real service `status` enum (never \"Booked\"). */\nfunction availabilityOf(status: MarketplaceService['status']): {\n label: string;\n icon: typeof CheckCircle2;\n className: string;\n} {\n switch (status) {\n case 'active':\n return {\n label: 'Available',\n icon: CheckCircle2,\n className: 'bg-status-success-bg text-status-success-text',\n };\n case 'pending':\n return {\n label: 'Pending',\n icon: Clock3,\n className: 'bg-status-warning-bg text-status-warning-text',\n };\n default:\n return {\n label: 'Unavailable',\n icon: PauseCircle,\n className: 'bg-bg-sunken text-text-secondary',\n };\n }\n}\n\nexport function MarketplaceMarketplacePagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { marketplaceServices } = useCrewFoundryStore();\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 9 });\n const [search, setSearch] = useState('');\n const [category, setCategory] = useState('all');\n\n const categoryTabs = useMemo<SegmentedTab[]>(() => {\n const counts = new Map<string, number>();\n for (const svc of marketplaceServices) {\n counts.set(svc.category, (counts.get(svc.category) ?? 0) + 1);\n }\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: marketplaceServices.length }];\n for (const [name, count] of [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {\n tabs.push({ id: name, label: name, count });\n }\n return tabs;\n }, [marketplaceServices]);\n\n const filtered = useMemo(() => {\n const query = search.trim().toLowerCase();\n return marketplaceServices.filter((svc) => {\n if (category !== 'all' && svc.category !== category) {\n return false;\n }\n if (!query) {\n return true;\n }\n return `${svc.title} ${svc.provider} ${svc.category} ${svc.skills.join(' ')}`\n .toLowerCase()\n .includes(query);\n });\n }, [marketplaceServices, search, category]);\n\n useEffect(() => {\n setPage(1);\n }, [search, category, setPage]);\n\n const avgRating =\n marketplaceServices.length > 0\n ? (\n marketplaceServices.reduce((s, m) => s + m.rating, 0) / marketplaceServices.length\n ).toFixed(1)\n : '0.0';\n const totalReviews = marketplaceServices.reduce((s, m) => s + m.reviews, 0);\n const pagedServices = filtered.slice(offset, offset + pageSize);\n const openService = (id: string) => navigate(`../marketplace/service-details-page?id=${id}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.marketplace.title', 'Talent Marketplace')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {t('crewfoundry.marketplace.subtitle', 'Book vetted specialists and services')}\n </p>\n <PagePurpose className=\"mt-1\">\n Discover freelancers and services for your projects — search, filter by category, and hire\n the right talent on demand.\n </PagePurpose>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {t('crewfoundry.marketplace.stats.services', 'Services')}\n </CardTitle>\n <ShoppingBag className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{marketplaceServices.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {t('crewfoundry.marketplace.stats.avgRating', 'Avg Rating')}\n </CardTitle>\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgRating}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {t('crewfoundry.marketplace.stats.totalReviews', 'Total Reviews')}\n </CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalReviews}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"relative w-full max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search services, providers, skills…\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n aria-label=\"Search marketplace services\"\n />\n </div>\n <div className=\"-mx-1 overflow-x-auto px-1 pb-1\">\n <SegmentedTabs\n tabs={categoryTabs}\n value={category}\n onChange={setCategory}\n aria-label=\"Filter services by category\"\n />\n </div>\n </div>\n\n {pagedServices.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No services found\"\n description=\"Try a different search term or category to find the help you need.\"\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {pagedServices.map((svc) => {\n const availability = availabilityOf(svc.status);\n const AvailabilityIcon = availability.icon;\n return (\n <GlassCard\n key={svc.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View and book ${svc.title} by ${svc.provider}`}\n onClick={() => openService(svc.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n openService(svc.id);\n }\n }}\n className=\"cursor-pointer border-border-seam transition-shadow hover:shadow-elevation-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <CardContent className=\"flex h-full flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2.5\">\n <span\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-action-primary-bg text-sm font-semibold text-action-primary-text\"\n aria-hidden=\"true\"\n >\n {getInitials(svc.provider)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{svc.provider}</p>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\">\n {svc.category}\n </span>\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-base font-bold tabular-nums\">\n ${svc.price.toLocaleString()}\n </p>\n <p className=\"text-[10px] text-text-secondary\">/{svc.priceType}</p>\n </div>\n </div>\n\n <p className=\"text-sm font-medium\">{svc.title}</p>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <RatingStars value={svc.rating} count={svc.reviews} showValue size={14} />\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${availability.className}`}\n >\n <AvailabilityIcon className=\"h-3 w-3\" />\n {availability.label}\n </span>\n </div>\n\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{svc.description}</p>\n\n <div className=\"flex flex-wrap gap-1\">\n {svc.skills.slice(0, 4).map((skill) => (\n <span\n key={skill}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\"\n >\n {skill}\n </span>\n ))}\n </div>\n\n <div className=\"mt-auto flex items-center justify-between pt-1 text-[11px] text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Clock3 className=\"h-3 w-3\" />\n {svc.deliveryTime}\n </span>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(event) => {\n event.stopPropagation();\n openService(svc.id);\n }}\n >\n {t('crewfoundry.marketplace.card.book', 'View & Book')}\n </Button>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {filtered.length > pageSize ? (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MarketplaceMarketplacePagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/MarketplaceMarketplacePagePage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport {\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle2, Clock3, PauseCircle, Search, ShoppingBag, Star, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type MarketplaceService } from '../store/crewfoundryStore';\nimport { RatingStars, SegmentedTabs, getInitials, type SegmentedTab } from '../components/viz';\n\n/** Honest availability derived from the real service `status` enum (never \"Booked\"). */\nfunction availabilityOf(status: MarketplaceService['status']): {\n label: string;\n icon: typeof CheckCircle2;\n className: string;\n} {\n switch (status) {\n case 'active':\n return {\n label: 'Available',\n icon: CheckCircle2,\n className: 'bg-status-success-bg text-status-success-text',\n };\n case 'pending':\n return {\n label: 'Pending',\n icon: Clock3,\n className: 'bg-status-warning-bg text-status-warning-text',\n };\n default:\n return {\n label: 'Unavailable',\n icon: PauseCircle,\n className: 'bg-bg-sunken text-text-secondary',\n };\n }\n}\n\nexport function MarketplaceMarketplacePagePage() {\n const tr = useTr();\n const navigate = useNavigate();\n const { marketplaceServices } = useCrewFoundryStore();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 9 });\n const [search, setSearch] = useState('');\n const [category, setCategory] = useState('all');\n\n const categoryTabs = useMemo<SegmentedTab[]>(() => {\n const counts = new Map<string, number>();\n for (const svc of marketplaceServices) {\n counts.set(svc.category, (counts.get(svc.category) ?? 0) + 1);\n }\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: marketplaceServices.length }];\n for (const [name, count] of [...counts.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {\n tabs.push({ id: name, label: name, count });\n }\n return tabs;\n }, [marketplaceServices]);\n\n const filtered = useMemo(() => {\n const query = search.trim().toLowerCase();\n return marketplaceServices.filter((svc) => {\n if (category !== 'all' && svc.category !== category) {\n return false;\n }\n if (!query) {\n return true;\n }\n return `${svc.title} ${svc.provider} ${svc.category} ${svc.skills.join(' ')}`\n .toLowerCase()\n .includes(query);\n });\n }, [marketplaceServices, search, category]);\n\n useEffect(() => {\n setPage(1);\n }, [search, category, setPage]);\n\n const avgRating =\n marketplaceServices.length > 0\n ? (\n marketplaceServices.reduce((s, m) => s + m.rating, 0) / marketplaceServices.length\n ).toFixed(1)\n : '0.0';\n const totalReviews = marketplaceServices.reduce((s, m) => s + m.reviews, 0);\n const pagedServices = filtered.slice(offset, offset + pageSize);\n const openService = (id: string) => navigate(`../marketplace/service-details-page?id=${id}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{tr('crewfoundry.marketplace.title', 'Talent Marketplace')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr('crewfoundry.marketplace.subtitle', 'Book vetted specialists and services')}\n </p>\n <PagePurpose className=\"mt-1\">\n Discover freelancers and services for your projects — search, filter by category, and hire\n the right talent on demand.\n </PagePurpose>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {tr('crewfoundry.marketplace.stats.services', 'Services')}\n </CardTitle>\n <ShoppingBag className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{marketplaceServices.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {tr('crewfoundry.marketplace.stats.avgRating', 'Avg Rating')}\n </CardTitle>\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgRating}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">\n {tr('crewfoundry.marketplace.stats.totalReviews', 'Total Reviews')}\n </CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalReviews}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"relative w-full max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search services, providers, skills…\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n aria-label=\"Search marketplace services\"\n />\n </div>\n <div className=\"-mx-1 overflow-x-auto px-1 pb-1\">\n <SegmentedTabs\n tabs={categoryTabs}\n value={category}\n onChange={setCategory}\n aria-label=\"Filter services by category\"\n />\n </div>\n </div>\n\n {pagedServices.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No services found\"\n description=\"Try a different search term or category to find the help you need.\"\n />\n ) : (\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {pagedServices.map((svc) => {\n const availability = availabilityOf(svc.status);\n const AvailabilityIcon = availability.icon;\n return (\n <GlassCard\n key={svc.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View and book ${svc.title} by ${svc.provider}`}\n onClick={() => openService(svc.id)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n openService(svc.id);\n }\n }}\n className=\"cursor-pointer border-border-seam transition-shadow hover:shadow-elevation-2 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <CardContent className=\"flex h-full flex-col gap-3 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex items-center gap-2.5\">\n <span\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-action-primary-bg text-sm font-semibold text-action-primary-text\"\n aria-hidden=\"true\"\n >\n {getInitials(svc.provider)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold\">{svc.provider}</p>\n <span className=\"inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\">\n {svc.category}\n </span>\n </div>\n </div>\n <div className=\"shrink-0 text-right\">\n <p className=\"text-base font-bold tabular-nums\">\n ${svc.price.toLocaleString()}\n </p>\n <p className=\"text-[10px] text-text-secondary\">/{svc.priceType}</p>\n </div>\n </div>\n\n <p className=\"text-sm font-medium\">{svc.title}</p>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <RatingStars value={svc.rating} count={svc.reviews} showValue size={14} />\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium ${availability.className}`}\n >\n <AvailabilityIcon className=\"h-3 w-3\" />\n {availability.label}\n </span>\n </div>\n\n <p className=\"line-clamp-2 text-xs text-text-secondary\">{svc.description}</p>\n\n <div className=\"flex flex-wrap gap-1\">\n {svc.skills.slice(0, 4).map((skill) => (\n <span\n key={skill}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\"\n >\n {skill}\n </span>\n ))}\n </div>\n\n <div className=\"mt-auto flex items-center justify-between pt-1 text-[11px] text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Clock3 className=\"h-3 w-3\" />\n {svc.deliveryTime}\n </span>\n </div>\n\n <Button\n className=\"w-full\"\n onClick={(event) => {\n event.stopPropagation();\n openService(svc.id);\n }}\n >\n {tr('crewfoundry.marketplace.card.book', 'View & Book')}\n </Button>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n )}\n\n {filtered.length > pageSize ? (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsProjectsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/ProjectsProjectsPagePage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,wBAAwB,
|
|
1
|
+
{"version":3,"file":"ProjectsProjectsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/ProjectsProjectsPagePage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,wBAAwB,gCA6SvC"}
|