@burdenoff/microfe-housingvista 2026.709.2 → 2026.709.3
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/housingvista/HousingVistaRoutes.d.ts.map +1 -1
- package/dist/housingvista/HousingVistaRoutes.js +136 -37
- package/dist/housingvista/HousingVistaRoutes.js.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.d.ts.map +1 -1
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js +4 -4
- package/dist/housingvista/hooks/useHousingVistaAnalytics.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +62 -60
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +141 -138
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +193 -191
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.d.ts.map +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +115 -113
- package/dist/housingvista/pages/chat/ChatPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js +152 -149
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.d.ts.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +141 -139
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +290 -287
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +64 -62
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +95 -93
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js +129 -126
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/AreaInsightsPage.js +99 -97
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +138 -136
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +99 -97
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js +137 -134
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.d.ts.map +1 -1
- package/dist/housingvista/pages/offers/OffersPage.js +94 -92
- package/dist/housingvista/pages/offers/OffersPage.js.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.d.ts.map +1 -1
- package/dist/housingvista/pages/properties/AddPropertyPage.js +280 -279
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.d.ts.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +153 -151
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +52 -50
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +54 -52
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.d.ts.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.js +43 -41
- package/dist/housingvista/pages/shortlist/ShortlistPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +211 -208
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +85 -83
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.js +25 -22
- package/dist/housingvista/pages/verification/VerificationDetailPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.d.ts.map +1 -1
- package/dist/housingvista/pages/verification/VerificationPage.js +123 -121
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.d.ts.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.js +128 -126
- package/dist/housingvista/pages/viewings/ViewingsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,16 +3,17 @@ import { PageLayout as n } from "../../components/PageLayout.js";
|
|
|
3
3
|
import { EmptyState as r } from "../../components/EmptyState.js";
|
|
4
4
|
import { FilterBar as i } from "../../components/FilterBar.js";
|
|
5
5
|
import { LoadingSkeleton as a } from "../../components/LoadingSkeleton.js";
|
|
6
|
-
import {
|
|
6
|
+
import { useTrackPageView as o } from "../../hooks/useHousingVistaAnalytics.js";
|
|
7
|
+
import { useLegalServices as s } from "../../hooks/index.js";
|
|
7
8
|
import "../../components/index.js";
|
|
8
|
-
import { useServerPagination as
|
|
9
|
-
import { useEffect as
|
|
10
|
-
import { BookOpen as
|
|
11
|
-
import { useNavigate as
|
|
12
|
-
import { Fragment as
|
|
13
|
-
import { EmphasisPanel as
|
|
9
|
+
import { useServerPagination as c } from "../../hooks/useServerPagination.js";
|
|
10
|
+
import { useEffect as l, useMemo as u, useState as d } from "react";
|
|
11
|
+
import { BookOpen as f, ClipboardCheck as p, Clock as m, FileText as h, Gavel as g, Scale as _, Search as v, Shield as y, Star as b } from "lucide-react";
|
|
12
|
+
import { useNavigate as x } from "react-router-dom";
|
|
13
|
+
import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
14
|
+
import { EmphasisPanel as T, NextSteps as E, Pagination as D } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/housingvista/pages/legal/LegalPage.tsx
|
|
15
|
-
var
|
|
16
|
+
var O = 9, k = [
|
|
16
17
|
{
|
|
17
18
|
value: "all",
|
|
18
19
|
label: "All"
|
|
@@ -38,17 +39,17 @@ var D = 9, O = [
|
|
|
38
39
|
label: "Document Review"
|
|
39
40
|
}
|
|
40
41
|
];
|
|
41
|
-
function
|
|
42
|
+
function A(e) {
|
|
42
43
|
switch (e) {
|
|
43
|
-
case "title_search": return /* @__PURE__ */
|
|
44
|
-
case "lease_review": return /* @__PURE__ */
|
|
45
|
-
case "dispute_resolution": return /* @__PURE__ */
|
|
46
|
-
case "registration": return /* @__PURE__ */
|
|
47
|
-
case "compliance": return /* @__PURE__ */
|
|
48
|
-
default: return /* @__PURE__ */
|
|
44
|
+
case "title_search": return /* @__PURE__ */ C(v, { className: "h-4 w-4" });
|
|
45
|
+
case "lease_review": return /* @__PURE__ */ C(h, { className: "h-4 w-4" });
|
|
46
|
+
case "dispute_resolution": return /* @__PURE__ */ C(g, { className: "h-4 w-4" });
|
|
47
|
+
case "registration": return /* @__PURE__ */ C(p, { className: "h-4 w-4" });
|
|
48
|
+
case "compliance": return /* @__PURE__ */ C(f, { className: "h-4 w-4" });
|
|
49
|
+
default: return /* @__PURE__ */ C(_, { className: "h-4 w-4" });
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
|
-
function
|
|
52
|
+
function j(e) {
|
|
52
53
|
switch (e) {
|
|
53
54
|
case "available": return "bg-status-success-bg-subtle text-status-success-text";
|
|
54
55
|
case "busy": return "bg-status-warning-bg-subtle text-status-warning-text";
|
|
@@ -56,95 +57,95 @@ function A(e) {
|
|
|
56
57
|
default: return "bg-bg-sunken text-text-secondary";
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
|
-
function
|
|
60
|
-
return /* @__PURE__ */
|
|
60
|
+
function M({ rating: t }) {
|
|
61
|
+
return /* @__PURE__ */ C("div", {
|
|
61
62
|
className: "flex items-center gap-0.5 text-status-warning-text",
|
|
62
|
-
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */
|
|
63
|
+
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */ C(b, { className: e("h-3 w-3", r < Math.floor(t) ? "fill-current" : "fill-none text-text-secondary/30") }, r))
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
|
-
function
|
|
66
|
-
let r =
|
|
67
|
-
return /* @__PURE__ */
|
|
66
|
+
function N({ service: n }) {
|
|
67
|
+
let r = x(), [i, a] = d(!1);
|
|
68
|
+
return /* @__PURE__ */ w("div", {
|
|
68
69
|
className: "flex flex-col rounded-card border border-border-seam bg-bg-surface p-5 shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2",
|
|
69
70
|
children: [
|
|
70
|
-
/* @__PURE__ */
|
|
71
|
+
/* @__PURE__ */ w("div", {
|
|
71
72
|
className: "mb-3 flex items-start justify-between gap-3",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ w("div", {
|
|
73
74
|
className: "flex items-start gap-3",
|
|
74
|
-
children: [/* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ C("div", {
|
|
75
76
|
className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue",
|
|
76
|
-
children:
|
|
77
|
-
}), /* @__PURE__ */
|
|
77
|
+
children: A(n.category)
|
|
78
|
+
}), /* @__PURE__ */ w("div", {
|
|
78
79
|
className: "min-w-0",
|
|
79
80
|
children: [
|
|
80
|
-
/* @__PURE__ */
|
|
81
|
+
/* @__PURE__ */ C("h3", {
|
|
81
82
|
className: "truncate text-sm font-semibold text-text-primary",
|
|
82
83
|
children: n.title
|
|
83
84
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
+
/* @__PURE__ */ C("p", {
|
|
85
86
|
className: "truncate text-xs text-text-secondary",
|
|
86
87
|
children: n.providerName
|
|
87
88
|
}),
|
|
88
|
-
/* @__PURE__ */
|
|
89
|
+
/* @__PURE__ */ w("div", {
|
|
89
90
|
className: "mt-1 flex items-center gap-1.5",
|
|
90
|
-
children: [/* @__PURE__ */
|
|
91
|
+
children: [/* @__PURE__ */ C(M, { rating: n.providerRating }), /* @__PURE__ */ C("span", {
|
|
91
92
|
className: "text-xs font-medium text-text-primary",
|
|
92
93
|
children: n.providerRating
|
|
93
94
|
})]
|
|
94
95
|
})
|
|
95
96
|
]
|
|
96
97
|
})]
|
|
97
|
-
}), /* @__PURE__ */
|
|
98
|
-
className: e("inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs font-medium capitalize",
|
|
98
|
+
}), /* @__PURE__ */ C("span", {
|
|
99
|
+
className: e("inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs font-medium capitalize", j(n.status)),
|
|
99
100
|
children: n.status
|
|
100
101
|
})]
|
|
101
102
|
}),
|
|
102
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ C("p", {
|
|
103
104
|
className: "mb-4 text-xs text-text-secondary line-clamp-2",
|
|
104
105
|
children: n.description
|
|
105
106
|
}),
|
|
106
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ w("div", {
|
|
107
108
|
className: "mb-4 grid grid-cols-2 gap-3",
|
|
108
|
-
children: [/* @__PURE__ */
|
|
109
|
+
children: [/* @__PURE__ */ w("div", {
|
|
109
110
|
className: "rounded-md bg-bg-sunken/50 p-2.5",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
111
|
+
children: [/* @__PURE__ */ w("p", {
|
|
111
112
|
className: "flex items-center gap-1 text-xs text-text-secondary",
|
|
112
|
-
children: [/* @__PURE__ */
|
|
113
|
-
}), /* @__PURE__ */
|
|
113
|
+
children: [/* @__PURE__ */ C(m, { className: "h-3 w-3" }), "Est. Duration"]
|
|
114
|
+
}), /* @__PURE__ */ C("p", {
|
|
114
115
|
className: "mt-0.5 text-sm font-semibold text-text-primary",
|
|
115
116
|
children: n.estimatedDuration
|
|
116
117
|
})]
|
|
117
|
-
}), /* @__PURE__ */
|
|
118
|
+
}), /* @__PURE__ */ w("div", {
|
|
118
119
|
className: "rounded-md bg-bg-sunken/50 p-2.5",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
120
|
+
children: [/* @__PURE__ */ C("p", {
|
|
120
121
|
className: "text-xs text-text-secondary",
|
|
121
122
|
children: "Price Range"
|
|
122
|
-
}), /* @__PURE__ */
|
|
123
|
+
}), /* @__PURE__ */ C("p", {
|
|
123
124
|
className: "mt-0.5 text-sm font-semibold text-text-primary",
|
|
124
125
|
children: n.priceRange
|
|
125
126
|
})]
|
|
126
127
|
})]
|
|
127
128
|
}),
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ C("div", {
|
|
129
130
|
className: "mb-3 text-xs",
|
|
130
|
-
children: n.consultationFee > 0 ? /* @__PURE__ */
|
|
131
|
+
children: n.consultationFee > 0 ? /* @__PURE__ */ w("span", {
|
|
131
132
|
className: "text-text-secondary",
|
|
132
133
|
children: [
|
|
133
134
|
"Consultation:",
|
|
134
135
|
" ",
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ C("span", {
|
|
136
137
|
className: "font-medium text-text-primary",
|
|
137
138
|
children: t(n.consultationFee)
|
|
138
139
|
})
|
|
139
140
|
]
|
|
140
|
-
}) : /* @__PURE__ */
|
|
141
|
+
}) : /* @__PURE__ */ C("span", {
|
|
141
142
|
className: "inline-flex items-center gap-1 font-medium text-status-success-text",
|
|
142
143
|
children: "Free consultation"
|
|
143
144
|
})
|
|
144
145
|
}),
|
|
145
|
-
n.documentsRequired.length > 0 && /* @__PURE__ */
|
|
146
|
+
n.documentsRequired.length > 0 && /* @__PURE__ */ w("div", {
|
|
146
147
|
className: "mb-4",
|
|
147
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ w("button", {
|
|
148
149
|
onClick: () => a(!i),
|
|
149
150
|
"aria-expanded": i,
|
|
150
151
|
className: "text-xs font-medium text-accent-blue hover:underline",
|
|
@@ -154,15 +155,15 @@ function M({ service: n }) {
|
|
|
154
155
|
n.documentsRequired.length,
|
|
155
156
|
")"
|
|
156
157
|
]
|
|
157
|
-
}), i && /* @__PURE__ */
|
|
158
|
+
}), i && /* @__PURE__ */ C("ul", {
|
|
158
159
|
className: "mt-1.5 space-y-1",
|
|
159
|
-
children: n.documentsRequired.map((e, t) => /* @__PURE__ */
|
|
160
|
+
children: n.documentsRequired.map((e, t) => /* @__PURE__ */ w("li", {
|
|
160
161
|
className: "flex items-center gap-1.5 text-xs text-text-secondary",
|
|
161
|
-
children: [/* @__PURE__ */
|
|
162
|
+
children: [/* @__PURE__ */ C("span", { className: "h-1 w-1 shrink-0 rounded-full bg-text-secondary/50" }), e]
|
|
162
163
|
}, t))
|
|
163
164
|
})]
|
|
164
165
|
}),
|
|
165
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ C("button", {
|
|
166
167
|
onClick: () => r(`../legal/${n.id}`),
|
|
167
168
|
disabled: n.status === "unavailable",
|
|
168
169
|
className: e("mt-auto w-full rounded-md px-4 py-2 text-xs font-medium transition-colors", n.status === "unavailable" ? "cursor-not-allowed bg-bg-sunken text-text-secondary" : "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover"),
|
|
@@ -171,84 +172,85 @@ function M({ service: n }) {
|
|
|
171
172
|
]
|
|
172
173
|
});
|
|
173
174
|
}
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
function P() {
|
|
176
|
+
o("housingvista.legal.viewed");
|
|
177
|
+
let [t, f] = d("all"), [p, m] = d(""), [h, g] = d(""), v = c(O), { setPage: b } = v;
|
|
178
|
+
l(() => {
|
|
179
|
+
let e = setTimeout(() => g(p), 300);
|
|
178
180
|
return () => clearTimeout(e);
|
|
179
|
-
}, [
|
|
180
|
-
let
|
|
181
|
+
}, [p]);
|
|
182
|
+
let x = u(() => {
|
|
181
183
|
let e = {};
|
|
182
|
-
return
|
|
183
|
-
}, [
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}, [
|
|
187
|
-
let { data:
|
|
188
|
-
filter:
|
|
189
|
-
pagination:
|
|
190
|
-
}),
|
|
191
|
-
return /* @__PURE__ */
|
|
184
|
+
return h.trim() && (e.search = h.trim()), t !== "all" && (e.category = t), Object.keys(e).length > 0 ? e : void 0;
|
|
185
|
+
}, [h, t]);
|
|
186
|
+
l(() => {
|
|
187
|
+
b(1);
|
|
188
|
+
}, [x, b]);
|
|
189
|
+
let { data: A, loading: j } = s({
|
|
190
|
+
filter: x,
|
|
191
|
+
pagination: v.pagination
|
|
192
|
+
}), M = A.length >= v.pageSize;
|
|
193
|
+
return /* @__PURE__ */ w(n, {
|
|
192
194
|
title: "Legal Services",
|
|
193
195
|
description: "Professional legal solutions for property transactions and documentation",
|
|
194
196
|
purpose: "Connect with verified property lawyers for the legal work behind every deal — title verification, rent agreements, registration, dispute advisory, and document review. Browse by category, compare ratings and fees, and book a consultation.",
|
|
195
|
-
actions: /* @__PURE__ */
|
|
197
|
+
actions: /* @__PURE__ */ w("span", {
|
|
196
198
|
className: "inline-flex items-center gap-1 text-xs text-status-success-text",
|
|
197
|
-
children: [/* @__PURE__ */
|
|
199
|
+
children: [/* @__PURE__ */ C(y, { className: "h-3.5 w-3.5" }), "Legally Compliant"]
|
|
198
200
|
}),
|
|
199
201
|
children: [
|
|
200
|
-
/* @__PURE__ */
|
|
202
|
+
/* @__PURE__ */ C(T, {
|
|
201
203
|
className: "flex flex-wrap items-center gap-2 p-3",
|
|
202
|
-
children:
|
|
203
|
-
onClick: () =>
|
|
204
|
+
children: k.map((n) => /* @__PURE__ */ C("button", {
|
|
205
|
+
onClick: () => f(n.value),
|
|
204
206
|
className: e("rounded-full px-4 py-1.5 text-sm font-medium transition-colors", t === n.value ? "bg-action-primary-bg text-action-primary-text" : "border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
|
|
205
207
|
children: n.label
|
|
206
208
|
}, n.value))
|
|
207
209
|
}),
|
|
208
|
-
/* @__PURE__ */
|
|
209
|
-
searchValue:
|
|
210
|
-
onSearchChange:
|
|
210
|
+
/* @__PURE__ */ C(i, {
|
|
211
|
+
searchValue: p,
|
|
212
|
+
onSearchChange: m,
|
|
211
213
|
searchPlaceholder: "Search legal services..."
|
|
212
214
|
}),
|
|
213
|
-
/* @__PURE__ */
|
|
215
|
+
/* @__PURE__ */ C(E, {
|
|
214
216
|
storageKey: "housingvista-legal",
|
|
215
217
|
steps: [
|
|
216
218
|
{
|
|
217
219
|
id: "verify-title",
|
|
218
220
|
label: "Verify a property title",
|
|
219
221
|
description: "The first legal check before any purchase — find a title-search expert.",
|
|
220
|
-
onClick: () =>
|
|
222
|
+
onClick: () => f("title_search")
|
|
221
223
|
},
|
|
222
224
|
{
|
|
223
225
|
id: "rent-agreement",
|
|
224
226
|
label: "Draft or review a rent agreement",
|
|
225
227
|
description: "Get a lawyer to prepare or vet your lease paperwork.",
|
|
226
|
-
onClick: () =>
|
|
228
|
+
onClick: () => f("lease_review")
|
|
227
229
|
},
|
|
228
230
|
{
|
|
229
231
|
id: "registration",
|
|
230
232
|
label: "Handle property registration",
|
|
231
233
|
description: "Find help to register a deal with the authorities.",
|
|
232
|
-
onClick: () =>
|
|
234
|
+
onClick: () => f("registration")
|
|
233
235
|
}
|
|
234
236
|
]
|
|
235
237
|
}),
|
|
236
|
-
|
|
238
|
+
j ? /* @__PURE__ */ C(a, {
|
|
237
239
|
variant: "card",
|
|
238
240
|
count: 6
|
|
239
|
-
}) :
|
|
241
|
+
}) : A.length > 0 ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C("div", {
|
|
240
242
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
241
|
-
children:
|
|
242
|
-
}), /* @__PURE__ */
|
|
243
|
-
page:
|
|
244
|
-
pageSize:
|
|
245
|
-
hasNextPage:
|
|
246
|
-
hasPreviousPage:
|
|
247
|
-
onPageChange:
|
|
248
|
-
onPageSizeChange:
|
|
249
|
-
})] }) : /* @__PURE__ */
|
|
243
|
+
children: A.map((e) => /* @__PURE__ */ C(N, { service: e }, e.id))
|
|
244
|
+
}), /* @__PURE__ */ C(D, {
|
|
245
|
+
page: v.page,
|
|
246
|
+
pageSize: v.pageSize,
|
|
247
|
+
hasNextPage: M,
|
|
248
|
+
hasPreviousPage: v.page > 1,
|
|
249
|
+
onPageChange: v.setPage,
|
|
250
|
+
onPageSizeChange: v.setPageSize
|
|
251
|
+
})] }) : /* @__PURE__ */ C(r, {
|
|
250
252
|
illustration: "empty-search",
|
|
251
|
-
icon: /* @__PURE__ */
|
|
253
|
+
icon: /* @__PURE__ */ C(_, { className: "h-12 w-12" }),
|
|
252
254
|
title: "No legal services found",
|
|
253
255
|
description: "Try adjusting your search or category filter."
|
|
254
256
|
})
|
|
@@ -256,6 +258,6 @@ function N() {
|
|
|
256
258
|
});
|
|
257
259
|
}
|
|
258
260
|
//#endregion
|
|
259
|
-
export {
|
|
261
|
+
export { P as LegalPage };
|
|
260
262
|
|
|
261
263
|
//# sourceMappingURL=LegalPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegalPage.js","names":[],"sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Scale,\n Star,\n Clock,\n FileText,\n Search as SearchIcon,\n Shield,\n Gavel,\n BookOpen,\n ClipboardCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState, LoadingSkeleton } from '../../components';\nimport { useLegalServices, type LegalServiceFilterInput } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport type { LegalService } from '../../types';\n\nconst PAGE_SIZE = 9;\n\ntype CategoryTab = 'all' | 'title_search' | 'lease_review' | 'dispute_resolution' | 'registration' | 'compliance';\n\nconst categoryTabs: { value: CategoryTab; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'title_search', label: 'Title Verification' },\n { value: 'lease_review', label: 'Rent Agreement' },\n { value: 'dispute_resolution', label: 'Advisory' },\n { value: 'registration', label: 'Registration' },\n { value: 'compliance', label: 'Document Review' },\n];\n\nfunction getCategoryIcon(category: string) {\n switch (category) {\n case 'title_search':\n return <SearchIcon className=\"h-4 w-4\" />;\n case 'lease_review':\n return <FileText className=\"h-4 w-4\" />;\n case 'dispute_resolution':\n return <Gavel className=\"h-4 w-4\" />;\n case 'registration':\n return <ClipboardCheck className=\"h-4 w-4\" />;\n case 'compliance':\n return <BookOpen className=\"h-4 w-4\" />;\n default:\n return <Scale className=\"h-4 w-4\" />;\n }\n}\n\nfunction getStatusStyles(status: string) {\n switch (status) {\n case 'available':\n return 'bg-status-success-bg-subtle text-status-success-text';\n case 'busy':\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n case 'unavailable':\n return 'bg-status-error-bg-subtle text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\nfunction RatingStars({ rating }: { rating: number }) {\n return (\n <div className=\"flex items-center gap-0.5 text-status-warning-text\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn('h-3 w-3', i < Math.floor(rating) ? 'fill-current' : 'fill-none text-text-secondary/30')}\n />\n ))}\n </div>\n );\n}\n\nfunction LegalServiceCard({ service }: { service: LegalService }) {\n const navigate = useNavigate();\n const [showDocs, setShowDocs] = useState(false);\n\n return (\n <div className=\"flex flex-col rounded-card border border-border-seam bg-bg-surface p-5 shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2\">\n {/* Header */}\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue\">\n {getCategoryIcon(service.category)}\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{service.title}</h3>\n <p className=\"truncate text-xs text-text-secondary\">{service.providerName}</p>\n <div className=\"mt-1 flex items-center gap-1.5\">\n <RatingStars rating={service.providerRating} />\n <span className=\"text-xs font-medium text-text-primary\">{service.providerRating}</span>\n </div>\n </div>\n </div>\n <span\n className={cn(\n 'inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs font-medium capitalize',\n getStatusStyles(service.status)\n )}\n >\n {service.status}\n </span>\n </div>\n\n {/* Description */}\n <p className=\"mb-4 text-xs text-text-secondary line-clamp-2\">{service.description}</p>\n\n {/* Facts grid — Duration + Price (mock parity) */}\n <div className=\"mb-4 grid grid-cols-2 gap-3\">\n <div className=\"rounded-md bg-bg-sunken/50 p-2.5\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n Est. Duration\n </p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">{service.estimatedDuration}</p>\n </div>\n <div className=\"rounded-md bg-bg-sunken/50 p-2.5\">\n <p className=\"text-xs text-text-secondary\">Price Range</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">{service.priceRange}</p>\n </div>\n </div>\n\n {/* Consultation fee */}\n <div className=\"mb-3 text-xs\">\n {service.consultationFee > 0 ? (\n <span className=\"text-text-secondary\">\n Consultation:{' '}\n <span className=\"font-medium text-text-primary\">{formatCurrency(service.consultationFee)}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 font-medium text-status-success-text\">Free consultation</span>\n )}\n </div>\n\n {/* Documents Required (collapsible) */}\n {service.documentsRequired.length > 0 && (\n <div className=\"mb-4\">\n <button\n onClick={() => setShowDocs(!showDocs)}\n aria-expanded={showDocs}\n className=\"text-xs font-medium text-accent-blue hover:underline\"\n >\n {showDocs ? 'Hide' : 'Show'} required documents ({service.documentsRequired.length})\n </button>\n {showDocs && (\n <ul className=\"mt-1.5 space-y-1\">\n {service.documentsRequired.map((doc, i) => (\n <li key={i} className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <span className=\"h-1 w-1 shrink-0 rounded-full bg-text-secondary/50\" />\n {doc}\n </li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n {/* Action */}\n <button\n onClick={() => navigate(`../legal/${service.id}`)}\n disabled={service.status === 'unavailable'}\n className={cn(\n 'mt-auto w-full rounded-md px-4 py-2 text-xs font-medium transition-colors',\n service.status === 'unavailable'\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover'\n )}\n >\n {service.status === 'unavailable' ? 'Currently Unavailable' : 'Book Now'}\n </button>\n </div>\n );\n}\n\nexport function LegalPage() {\n const [activeCategory, setActiveCategory] = useState<CategoryTab>('all');\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const pagination = useServerPagination(PAGE_SIZE);\n const { setPage } = pagination;\n\n // Debounce the search box so we don't refetch on every keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n // Server-side filter — category + search are resolved by the subgraph.\n const serverFilter = useMemo<LegalServiceFilterInput | undefined>(() => {\n const filter: LegalServiceFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (activeCategory !== 'all') filter.category = activeCategory;\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, activeCategory]);\n\n // Reset to the first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: services, loading } = useLegalServices({ filter: serverFilter, pagination: pagination.pagination });\n\n const hasNextPage = services.length >= pagination.pageSize;\n\n return (\n <PageLayout\n title=\"Legal Services\"\n description=\"Professional legal solutions for property transactions and documentation\"\n purpose=\"Connect with verified property lawyers for the legal work behind every deal — title verification, rent agreements, registration, dispute advisory, and document review. Browse by category, compare ratings and fees, and book a consultation.\"\n actions={\n <span className=\"inline-flex items-center gap-1 text-xs text-status-success-text\">\n <Shield className=\"h-3.5 w-3.5\" />\n Legally Compliant\n </span>\n }\n >\n {/* Category Tabs — primary control zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"flex flex-wrap items-center gap-2 p-3\">\n {categoryTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setActiveCategory(tab.value)}\n className={cn(\n 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',\n activeCategory === tab.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </EmphasisPanel>\n\n {/* Search */}\n <FilterBar searchValue={search} onSearchChange={setSearch} searchPlaceholder=\"Search legal services...\" />\n\n <NextSteps\n storageKey=\"housingvista-legal\"\n steps={[\n {\n id: 'verify-title',\n label: 'Verify a property title',\n description: 'The first legal check before any purchase — find a title-search expert.',\n onClick: () => setActiveCategory('title_search'),\n },\n {\n id: 'rent-agreement',\n label: 'Draft or review a rent agreement',\n description: 'Get a lawyer to prepare or vet your lease paperwork.',\n onClick: () => setActiveCategory('lease_review'),\n },\n {\n id: 'registration',\n label: 'Handle property registration',\n description: 'Find help to register a deal with the authorities.',\n onClick: () => setActiveCategory('registration'),\n },\n ]}\n />\n\n {/* Service Cards */}\n {loading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : services.length > 0 ? (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {services.map((service) => (\n <LegalServiceCard key={service.id} service={service} />\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Scale className=\"h-12 w-12\" />}\n title=\"No legal services found\"\n description=\"Try adjusting your search or category filter.\"\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LegalPage.js","names":[],"sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Scale,\n Star,\n Clock,\n FileText,\n Search as SearchIcon,\n Shield,\n Gavel,\n BookOpen,\n ClipboardCheck,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps, Pagination } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState, LoadingSkeleton } from '../../components';\nimport { useLegalServices, type LegalServiceFilterInput } from '../../hooks';\nimport { useServerPagination } from '../../hooks/useServerPagination';\nimport { cn, formatCurrency } from '../../utils/helpers';\nimport type { LegalService } from '../../types';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\n\nconst PAGE_SIZE = 9;\n\ntype CategoryTab = 'all' | 'title_search' | 'lease_review' | 'dispute_resolution' | 'registration' | 'compliance';\n\nconst categoryTabs: { value: CategoryTab; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'title_search', label: 'Title Verification' },\n { value: 'lease_review', label: 'Rent Agreement' },\n { value: 'dispute_resolution', label: 'Advisory' },\n { value: 'registration', label: 'Registration' },\n { value: 'compliance', label: 'Document Review' },\n];\n\nfunction getCategoryIcon(category: string) {\n switch (category) {\n case 'title_search':\n return <SearchIcon className=\"h-4 w-4\" />;\n case 'lease_review':\n return <FileText className=\"h-4 w-4\" />;\n case 'dispute_resolution':\n return <Gavel className=\"h-4 w-4\" />;\n case 'registration':\n return <ClipboardCheck className=\"h-4 w-4\" />;\n case 'compliance':\n return <BookOpen className=\"h-4 w-4\" />;\n default:\n return <Scale className=\"h-4 w-4\" />;\n }\n}\n\nfunction getStatusStyles(status: string) {\n switch (status) {\n case 'available':\n return 'bg-status-success-bg-subtle text-status-success-text';\n case 'busy':\n return 'bg-status-warning-bg-subtle text-status-warning-text';\n case 'unavailable':\n return 'bg-status-error-bg-subtle text-status-error-text';\n default:\n return 'bg-bg-sunken text-text-secondary';\n }\n}\n\nfunction RatingStars({ rating }: { rating: number }) {\n return (\n <div className=\"flex items-center gap-0.5 text-status-warning-text\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn('h-3 w-3', i < Math.floor(rating) ? 'fill-current' : 'fill-none text-text-secondary/30')}\n />\n ))}\n </div>\n );\n}\n\nfunction LegalServiceCard({ service }: { service: LegalService }) {\n const navigate = useNavigate();\n const [showDocs, setShowDocs] = useState(false);\n\n return (\n <div className=\"flex flex-col rounded-card border border-border-seam bg-bg-surface p-5 shadow-elevation-1 transition-[box-shadow,border-color] hover:border-border-strong hover:shadow-elevation-2\">\n {/* Header */}\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-accent-blue-subtle text-accent-blue\">\n {getCategoryIcon(service.category)}\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-sm font-semibold text-text-primary\">{service.title}</h3>\n <p className=\"truncate text-xs text-text-secondary\">{service.providerName}</p>\n <div className=\"mt-1 flex items-center gap-1.5\">\n <RatingStars rating={service.providerRating} />\n <span className=\"text-xs font-medium text-text-primary\">{service.providerRating}</span>\n </div>\n </div>\n </div>\n <span\n className={cn(\n 'inline-flex shrink-0 rounded-full px-2 py-0.5 text-xs font-medium capitalize',\n getStatusStyles(service.status)\n )}\n >\n {service.status}\n </span>\n </div>\n\n {/* Description */}\n <p className=\"mb-4 text-xs text-text-secondary line-clamp-2\">{service.description}</p>\n\n {/* Facts grid — Duration + Price (mock parity) */}\n <div className=\"mb-4 grid grid-cols-2 gap-3\">\n <div className=\"rounded-md bg-bg-sunken/50 p-2.5\">\n <p className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n Est. Duration\n </p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">{service.estimatedDuration}</p>\n </div>\n <div className=\"rounded-md bg-bg-sunken/50 p-2.5\">\n <p className=\"text-xs text-text-secondary\">Price Range</p>\n <p className=\"mt-0.5 text-sm font-semibold text-text-primary\">{service.priceRange}</p>\n </div>\n </div>\n\n {/* Consultation fee */}\n <div className=\"mb-3 text-xs\">\n {service.consultationFee > 0 ? (\n <span className=\"text-text-secondary\">\n Consultation:{' '}\n <span className=\"font-medium text-text-primary\">{formatCurrency(service.consultationFee)}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 font-medium text-status-success-text\">Free consultation</span>\n )}\n </div>\n\n {/* Documents Required (collapsible) */}\n {service.documentsRequired.length > 0 && (\n <div className=\"mb-4\">\n <button\n onClick={() => setShowDocs(!showDocs)}\n aria-expanded={showDocs}\n className=\"text-xs font-medium text-accent-blue hover:underline\"\n >\n {showDocs ? 'Hide' : 'Show'} required documents ({service.documentsRequired.length})\n </button>\n {showDocs && (\n <ul className=\"mt-1.5 space-y-1\">\n {service.documentsRequired.map((doc, i) => (\n <li key={i} className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <span className=\"h-1 w-1 shrink-0 rounded-full bg-text-secondary/50\" />\n {doc}\n </li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n {/* Action */}\n <button\n onClick={() => navigate(`../legal/${service.id}`)}\n disabled={service.status === 'unavailable'}\n className={cn(\n 'mt-auto w-full rounded-md px-4 py-2 text-xs font-medium transition-colors',\n service.status === 'unavailable'\n ? 'cursor-not-allowed bg-bg-sunken text-text-secondary'\n : 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover'\n )}\n >\n {service.status === 'unavailable' ? 'Currently Unavailable' : 'Book Now'}\n </button>\n </div>\n );\n}\n\nexport function LegalPage() {\n useTrackPageView('housingvista.legal.viewed');\n const [activeCategory, setActiveCategory] = useState<CategoryTab>('all');\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const pagination = useServerPagination(PAGE_SIZE);\n const { setPage } = pagination;\n\n // Debounce the search box so we don't refetch on every keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedSearch(search), 300);\n return () => clearTimeout(timer);\n }, [search]);\n\n // Server-side filter — category + search are resolved by the subgraph.\n const serverFilter = useMemo<LegalServiceFilterInput | undefined>(() => {\n const filter: LegalServiceFilterInput = {};\n if (debouncedSearch.trim()) filter.search = debouncedSearch.trim();\n if (activeCategory !== 'all') filter.category = activeCategory;\n return Object.keys(filter).length > 0 ? filter : undefined;\n }, [debouncedSearch, activeCategory]);\n\n // Reset to the first page whenever the server-side filter changes.\n useEffect(() => {\n setPage(1);\n }, [serverFilter, setPage]);\n\n const { data: services, loading } = useLegalServices({ filter: serverFilter, pagination: pagination.pagination });\n\n const hasNextPage = services.length >= pagination.pageSize;\n\n return (\n <PageLayout\n title=\"Legal Services\"\n description=\"Professional legal solutions for property transactions and documentation\"\n purpose=\"Connect with verified property lawyers for the legal work behind every deal — title verification, rent agreements, registration, dispute advisory, and document review. Browse by category, compare ratings and fees, and book a consultation.\"\n actions={\n <span className=\"inline-flex items-center gap-1 text-xs text-status-success-text\">\n <Shield className=\"h-3.5 w-3.5\" />\n Legally Compliant\n </span>\n }\n >\n {/* Category Tabs — primary control zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"flex flex-wrap items-center gap-2 p-3\">\n {categoryTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setActiveCategory(tab.value)}\n className={cn(\n 'rounded-full px-4 py-1.5 text-sm font-medium transition-colors',\n activeCategory === tab.value\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </EmphasisPanel>\n\n {/* Search */}\n <FilterBar searchValue={search} onSearchChange={setSearch} searchPlaceholder=\"Search legal services...\" />\n\n <NextSteps\n storageKey=\"housingvista-legal\"\n steps={[\n {\n id: 'verify-title',\n label: 'Verify a property title',\n description: 'The first legal check before any purchase — find a title-search expert.',\n onClick: () => setActiveCategory('title_search'),\n },\n {\n id: 'rent-agreement',\n label: 'Draft or review a rent agreement',\n description: 'Get a lawyer to prepare or vet your lease paperwork.',\n onClick: () => setActiveCategory('lease_review'),\n },\n {\n id: 'registration',\n label: 'Handle property registration',\n description: 'Find help to register a deal with the authorities.',\n onClick: () => setActiveCategory('registration'),\n },\n ]}\n />\n\n {/* Service Cards */}\n {loading ? (\n <LoadingSkeleton variant=\"card\" count={6} />\n ) : services.length > 0 ? (\n <>\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {services.map((service) => (\n <LegalServiceCard key={service.id} service={service} />\n ))}\n </div>\n <Pagination\n page={pagination.page}\n pageSize={pagination.pageSize}\n hasNextPage={hasNextPage}\n hasPreviousPage={pagination.page > 1}\n onPageChange={pagination.setPage}\n onPageSizeChange={pagination.setPageSize}\n />\n </>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Scale className=\"h-12 w-12\" />}\n title=\"No legal services found\"\n description=\"Try adjusting your search or category filter.\"\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegalServiceDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/legal/LegalServiceDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"LegalServiceDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/legal/LegalServiceDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA2EH,wBAAgB,sBAAsB,gCAiOrC"}
|