@burdenoff/microfe-housingvista 2026.625.2 → 2026.626.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/housingvista/pages/amc/AMCManagementPage.js +109 -82
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetManagementPage.js +57 -31
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoansPage.js +190 -162
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +57 -31
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +36 -13
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +36 -11
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +95 -69
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -10,9 +10,9 @@ import { useMemo as l, useState as u } from "react";
|
|
|
10
10
|
import { ArrowDownRight as d, ArrowUpRight as f, BarChart3 as p, Building as m, Calendar as h, DollarSign as g, Eye as _, PieChart as v, Plus as y, Target as b, TrendingUp as x } from "lucide-react";
|
|
11
11
|
import { useNavigate as S } from "react-router-dom";
|
|
12
12
|
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
13
|
-
import { EmphasisPanel as T } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
import { EmphasisPanel as T, NextSteps as E } from "@burdenoff/fe-libs/ui";
|
|
14
14
|
//#region src/housingvista/pages/investments/InvestmentsPage.tsx
|
|
15
|
-
var
|
|
15
|
+
var D = [
|
|
16
16
|
{
|
|
17
17
|
value: "all",
|
|
18
18
|
label: "All"
|
|
@@ -30,7 +30,7 @@ var E = [
|
|
|
30
30
|
label: "Funding"
|
|
31
31
|
}
|
|
32
32
|
];
|
|
33
|
-
function
|
|
33
|
+
function O(e) {
|
|
34
34
|
switch (e) {
|
|
35
35
|
case "active": return "success";
|
|
36
36
|
case "funding": return "warning";
|
|
@@ -41,26 +41,26 @@ function D(e) {
|
|
|
41
41
|
default: return "default";
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function k(e) {
|
|
45
45
|
return e > 0 ? "text-emerald-600 dark:text-emerald-400" : e < 0 ? "text-red-600 dark:text-red-400" : "text-text-secondary";
|
|
46
46
|
}
|
|
47
|
-
function
|
|
48
|
-
let
|
|
49
|
-
let t = e.property.title.toLowerCase().includes(
|
|
47
|
+
function A() {
|
|
48
|
+
let A = S(), { data: j } = c(), [M, N] = u(""), [P, F] = u("all"), I = l(() => j.filter((e) => {
|
|
49
|
+
let t = e.property.title.toLowerCase().includes(M.toLowerCase()) || e.id.toLowerCase().includes(M.toLowerCase()), n = P === "all" || e.status === P;
|
|
50
50
|
return t && n;
|
|
51
51
|
}), [
|
|
52
|
-
A,
|
|
53
52
|
j,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
M,
|
|
54
|
+
P
|
|
55
|
+
]), L = l(() => {
|
|
56
|
+
let e = j.reduce((e, t) => e + t.investors.reduce((e, t) => e + t.investmentAmount, 0), 0), t = j.reduce((e, t) => e + t.returns.reduce((e, t) => e + t.amount, 0), 0);
|
|
57
57
|
return {
|
|
58
58
|
totalInvested: e,
|
|
59
59
|
currentValue: e + t,
|
|
60
60
|
totalReturns: t,
|
|
61
|
-
activeCount:
|
|
61
|
+
activeCount: j.filter((e) => e.status === "active").length
|
|
62
62
|
};
|
|
63
|
-
}, [
|
|
63
|
+
}, [j]);
|
|
64
64
|
return /* @__PURE__ */ w(r, {
|
|
65
65
|
title: "Investments",
|
|
66
66
|
description: "Track your real estate investment portfolio and performance",
|
|
@@ -77,7 +77,7 @@ function k() {
|
|
|
77
77
|
children: [
|
|
78
78
|
/* @__PURE__ */ C(i, {
|
|
79
79
|
label: "Total Invested",
|
|
80
|
-
value:
|
|
80
|
+
value: L.totalInvested,
|
|
81
81
|
prefix: "$",
|
|
82
82
|
icon: /* @__PURE__ */ C(g, { className: "h-4 w-4" }),
|
|
83
83
|
trend: "up",
|
|
@@ -85,7 +85,7 @@ function k() {
|
|
|
85
85
|
}),
|
|
86
86
|
/* @__PURE__ */ C(i, {
|
|
87
87
|
label: "Current Value",
|
|
88
|
-
value:
|
|
88
|
+
value: L.currentValue,
|
|
89
89
|
prefix: "$",
|
|
90
90
|
icon: /* @__PURE__ */ C(x, { className: "h-4 w-4" }),
|
|
91
91
|
trend: "up",
|
|
@@ -93,7 +93,7 @@ function k() {
|
|
|
93
93
|
}),
|
|
94
94
|
/* @__PURE__ */ C(i, {
|
|
95
95
|
label: "Total Returns",
|
|
96
|
-
value:
|
|
96
|
+
value: L.totalReturns,
|
|
97
97
|
prefix: "$",
|
|
98
98
|
icon: /* @__PURE__ */ C(v, { className: "h-4 w-4" }),
|
|
99
99
|
trend: "up",
|
|
@@ -101,42 +101,68 @@ function k() {
|
|
|
101
101
|
}),
|
|
102
102
|
/* @__PURE__ */ C(i, {
|
|
103
103
|
label: "Active Investments",
|
|
104
|
-
value:
|
|
104
|
+
value: L.activeCount,
|
|
105
105
|
icon: /* @__PURE__ */ C(p, { className: "h-4 w-4" })
|
|
106
106
|
})
|
|
107
107
|
]
|
|
108
108
|
})
|
|
109
109
|
}),
|
|
110
|
+
/* @__PURE__ */ C(E, {
|
|
111
|
+
storageKey: "housingvista-investments",
|
|
112
|
+
steps: [
|
|
113
|
+
{
|
|
114
|
+
id: "review-active",
|
|
115
|
+
label: "Review your active investments",
|
|
116
|
+
description: "Filter to the active deals currently paying returns.",
|
|
117
|
+
onClick: () => F("active")
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "find-funding",
|
|
121
|
+
label: "Find funding opportunities",
|
|
122
|
+
description: "See deals still raising capital that you can join.",
|
|
123
|
+
onClick: () => F("funding")
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "open-detail",
|
|
127
|
+
label: "Open an investment for full details",
|
|
128
|
+
description: "Drill into returns, investors, and risk for any holding.",
|
|
129
|
+
onClick: () => {
|
|
130
|
+
let e = j[0];
|
|
131
|
+
e && A(`/investments/${e.id}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}),
|
|
110
136
|
/* @__PURE__ */ C("div", {
|
|
111
137
|
className: "flex flex-wrap items-center gap-2",
|
|
112
|
-
children:
|
|
113
|
-
onClick: () =>
|
|
114
|
-
className: e("rounded-md px-3 py-1.5 text-sm font-medium transition-colors",
|
|
138
|
+
children: D.map((t) => /* @__PURE__ */ C("button", {
|
|
139
|
+
onClick: () => F(t.value),
|
|
140
|
+
className: e("rounded-md px-3 py-1.5 text-sm font-medium transition-colors", P === t.value ? "bg-foreground text-background" : "bg-bg-sunken text-text-secondary hover:text-text-primary"),
|
|
115
141
|
children: t.label
|
|
116
142
|
}, t.value))
|
|
117
143
|
}),
|
|
118
144
|
/* @__PURE__ */ C(s, {
|
|
119
|
-
searchValue:
|
|
120
|
-
onSearchChange:
|
|
145
|
+
searchValue: M,
|
|
146
|
+
onSearchChange: N,
|
|
121
147
|
searchPlaceholder: "Search by property or investment ID..."
|
|
122
148
|
}),
|
|
123
149
|
/* @__PURE__ */ w("p", {
|
|
124
150
|
className: "text-sm text-text-secondary",
|
|
125
151
|
children: [
|
|
126
152
|
"Showing ",
|
|
127
|
-
|
|
153
|
+
I.length,
|
|
128
154
|
" of ",
|
|
129
|
-
|
|
155
|
+
j.length,
|
|
130
156
|
" investments"
|
|
131
157
|
]
|
|
132
158
|
}),
|
|
133
|
-
|
|
159
|
+
I.length > 0 ? /* @__PURE__ */ C("div", {
|
|
134
160
|
className: "space-y-4",
|
|
135
|
-
children:
|
|
161
|
+
children: I.map((r) => {
|
|
136
162
|
let i = r.investors.reduce((e, t) => e + t.investmentAmount, 0), o = r.returns.reduce((e, t) => e + t.amount, 0), s = i + o, c = i > 0 ? o / i * 100 : 0;
|
|
137
163
|
return /* @__PURE__ */ C("div", {
|
|
138
164
|
className: "cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md",
|
|
139
|
-
onClick: () =>
|
|
165
|
+
onClick: () => A(`/investments/${r.id}`),
|
|
140
166
|
children: /* @__PURE__ */ w("div", {
|
|
141
167
|
className: "flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between",
|
|
142
168
|
children: [/* @__PURE__ */ C("div", {
|
|
@@ -158,7 +184,7 @@ function k() {
|
|
|
158
184
|
}),
|
|
159
185
|
/* @__PURE__ */ C(a, {
|
|
160
186
|
label: r.status,
|
|
161
|
-
variant:
|
|
187
|
+
variant: O(r.status)
|
|
162
188
|
}),
|
|
163
189
|
/* @__PURE__ */ C(a, {
|
|
164
190
|
label: r.investmentType.replace("_", " "),
|
|
@@ -187,7 +213,7 @@ function k() {
|
|
|
187
213
|
className: "text-text-secondary",
|
|
188
214
|
children: "Total Return"
|
|
189
215
|
}), /* @__PURE__ */ w("div", {
|
|
190
|
-
className: e("font-medium",
|
|
216
|
+
className: e("font-medium", k(o)),
|
|
191
217
|
children: [/* @__PURE__ */ w("div", {
|
|
192
218
|
className: "flex items-center gap-1",
|
|
193
219
|
children: [
|
|
@@ -246,7 +272,7 @@ function k() {
|
|
|
246
272
|
className: "flex shrink-0 gap-2",
|
|
247
273
|
children: /* @__PURE__ */ w("button", {
|
|
248
274
|
onClick: (e) => {
|
|
249
|
-
e.stopPropagation(),
|
|
275
|
+
e.stopPropagation(), A(`/investments/${r.id}`);
|
|
250
276
|
},
|
|
251
277
|
className: "inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
252
278
|
children: [/* @__PURE__ */ C(_, { className: "h-4 w-4" }), "View Details"]
|
|
@@ -269,6 +295,6 @@ function k() {
|
|
|
269
295
|
});
|
|
270
296
|
}
|
|
271
297
|
//#endregion
|
|
272
|
-
export {
|
|
298
|
+
export { A as InvestmentsPage };
|
|
273
299
|
|
|
274
300
|
//# sourceMappingURL=InvestmentsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvestmentsPage.js","names":[],"sources":["../../../../src/housingvista/pages/investments/InvestmentsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n DollarSign,\n TrendingUp,\n PieChart,\n BarChart3,\n Plus,\n Building,\n ArrowUpRight,\n ArrowDownRight,\n Eye,\n Target,\n Calendar,\n} from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { FilterBar } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useInvestments } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { Investment, InvestmentStatus } from '../../types';\n\nconst statusTabs: { value: InvestmentStatus | 'all'; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'closed', label: 'Completed' },\n { value: 'funding', label: 'Funding' },\n];\n\nfunction getStatusVariant(status: InvestmentStatus): 'success' | 'warning' | 'error' | 'info' | 'muted' | 'default' {\n switch (status) {\n case 'active':\n return 'success';\n case 'funding':\n return 'warning';\n case 'closed':\n case 'mature':\n return 'info';\n case 'draft':\n case 'open':\n return 'muted';\n default:\n return 'default';\n }\n}\n\nfunction getReturnColor(returnValue: number) {\n if (returnValue > 0) return 'text-emerald-600 dark:text-emerald-400';\n if (returnValue < 0) return 'text-red-600 dark:text-red-400';\n return 'text-text-secondary';\n}\n\nexport function InvestmentsPage() {\n const navigate = useNavigate();\n const { data: investments } = useInvestments();\n const [search, setSearch] = useState('');\n const [activeTab, setActiveTab] = useState<InvestmentStatus | 'all'>('all');\n\n const filtered = useMemo(() => {\n return investments.filter((inv) => {\n const matchesSearch =\n inv.property.title.toLowerCase().includes(search.toLowerCase()) ||\n inv.id.toLowerCase().includes(search.toLowerCase());\n const matchesTab = activeTab === 'all' || inv.status === activeTab;\n return matchesSearch && matchesTab;\n });\n }, [investments, search, activeTab]);\n\n const portfolioStats = useMemo(() => {\n const totalInvested = investments.reduce((sum, inv) => {\n return sum + inv.investors.reduce((s, i) => s + i.investmentAmount, 0);\n }, 0);\n const totalReturns = investments.reduce((sum, inv) => {\n return sum + inv.returns.reduce((s, r) => s + r.amount, 0);\n }, 0);\n const currentValue = totalInvested + totalReturns;\n const activeCount = investments.filter((inv) => inv.status === 'active').length;\n return { totalInvested, currentValue, totalReturns, activeCount };\n }, [investments]);\n\n return (\n <PageLayout\n title=\"Investments\"\n description=\"Track your real estate investment portfolio and performance\"\n purpose=\"Follow every real-estate investment you hold in one portfolio — how much you put in, what it is worth now, the returns it has paid, and its risk level. Use it to judge performance and decide where to put your next dollar.\"\n actions={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n >\n {/* Portfolio Summary Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Invested\"\n value={portfolioStats.totalInvested}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n trend=\"up\"\n change={12}\n />\n <StatCard\n label=\"Current Value\"\n value={portfolioStats.currentValue}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n trend=\"up\"\n change={8}\n />\n <StatCard\n label=\"Total Returns\"\n value={portfolioStats.totalReturns}\n prefix=\"$\"\n icon={<PieChart className=\"h-4 w-4\" />}\n trend=\"up\"\n change={15}\n />\n <StatCard\n label=\"Active Investments\"\n value={portfolioStats.activeCount}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n {/* Filter Tabs */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {statusTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setActiveTab(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n activeTab === tab.value\n ? 'bg-foreground text-background'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search by property or investment ID...\"\n />\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n Showing {filtered.length} of {investments.length} investments\n </p>\n\n {/* Investment Cards */}\n {filtered.length > 0 ? (\n <div className=\"space-y-4\">\n {filtered.map((investment) => {\n const totalInvested = investment.investors.reduce((s, i) => s + i.investmentAmount, 0);\n const totalReturns = investment.returns.reduce((s, r) => s + r.amount, 0);\n const currentValue = totalInvested + totalReturns;\n const returnPercentage = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n\n return (\n <div\n key={investment.id}\n className=\"cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\"\n onClick={() => navigate(`/investments/${investment.id}`)}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex-1\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2\">\n <Building className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"flex-1\">\n <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{investment.property.title}</h3>\n <StatusBadge label={investment.status} variant={getStatusVariant(investment.status)} />\n <StatusBadge label={investment.investmentType.replace('_', ' ')} variant=\"muted\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 text-sm sm:grid-cols-2 lg:grid-cols-4\">\n <div>\n <p className=\"text-text-secondary\">Invested Amount</p>\n <p className=\"text-lg font-medium text-text-primary\">{formatCurrency(totalInvested)}</p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-medium text-text-primary\">{formatCurrency(currentValue)}</p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Total Return</p>\n <div className={cn('font-medium', getReturnColor(totalReturns))}>\n <div className=\"flex items-center gap-1\">\n {totalReturns > 0 && <ArrowUpRight className=\"h-3 w-3\" />}\n {totalReturns < 0 && <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(totalReturns)}\n </div>\n <div className=\"text-sm\">{returnPercentage.toFixed(1)}%</div>\n </div>\n </div>\n <div>\n <p className=\"text-text-secondary\">Risk Level</p>\n <p className=\"font-medium capitalize text-text-primary\">{investment.riskLevel}</p>\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <TrendingUp className=\"h-3 w-3\" />\n Start: {formatDate(investment.startDate.toISOString())}\n </span>\n <span className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3\" />\n Type: {investment.investmentType.replace('_', ' ')}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Returns: {investment.returns.length} payments\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex shrink-0 gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`/investments/${investment.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-4 w-4\" />\n View Details\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<PieChart className=\"h-12 w-12\" />}\n title=\"No investments found\"\n description=\"Try adjusting your search criteria or explore new investment opportunities.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n Explore Investments\n </button>\n }\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InvestmentsPage.js","names":[],"sources":["../../../../src/housingvista/pages/investments/InvestmentsPage.tsx"],"sourcesContent":["import { useState, useMemo } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n DollarSign,\n TrendingUp,\n PieChart,\n BarChart3,\n Plus,\n Building,\n ArrowUpRight,\n ArrowDownRight,\n Eye,\n Target,\n Calendar,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components';\nimport { StatCard } from '../../components';\nimport { FilterBar } from '../../components';\nimport { StatusBadge } from '../../components';\nimport { EmptyState } from '../../components';\nimport { useInvestments } from '../../hooks';\nimport { cn, formatCurrency, formatDate } from '../../utils/helpers';\nimport type { Investment, InvestmentStatus } from '../../types';\n\nconst statusTabs: { value: InvestmentStatus | 'all'; label: string }[] = [\n { value: 'all', label: 'All' },\n { value: 'active', label: 'Active' },\n { value: 'closed', label: 'Completed' },\n { value: 'funding', label: 'Funding' },\n];\n\nfunction getStatusVariant(status: InvestmentStatus): 'success' | 'warning' | 'error' | 'info' | 'muted' | 'default' {\n switch (status) {\n case 'active':\n return 'success';\n case 'funding':\n return 'warning';\n case 'closed':\n case 'mature':\n return 'info';\n case 'draft':\n case 'open':\n return 'muted';\n default:\n return 'default';\n }\n}\n\nfunction getReturnColor(returnValue: number) {\n if (returnValue > 0) return 'text-emerald-600 dark:text-emerald-400';\n if (returnValue < 0) return 'text-red-600 dark:text-red-400';\n return 'text-text-secondary';\n}\n\nexport function InvestmentsPage() {\n const navigate = useNavigate();\n const { data: investments } = useInvestments();\n const [search, setSearch] = useState('');\n const [activeTab, setActiveTab] = useState<InvestmentStatus | 'all'>('all');\n\n const filtered = useMemo(() => {\n return investments.filter((inv) => {\n const matchesSearch =\n inv.property.title.toLowerCase().includes(search.toLowerCase()) ||\n inv.id.toLowerCase().includes(search.toLowerCase());\n const matchesTab = activeTab === 'all' || inv.status === activeTab;\n return matchesSearch && matchesTab;\n });\n }, [investments, search, activeTab]);\n\n const portfolioStats = useMemo(() => {\n const totalInvested = investments.reduce((sum, inv) => {\n return sum + inv.investors.reduce((s, i) => s + i.investmentAmount, 0);\n }, 0);\n const totalReturns = investments.reduce((sum, inv) => {\n return sum + inv.returns.reduce((s, r) => s + r.amount, 0);\n }, 0);\n const currentValue = totalInvested + totalReturns;\n const activeCount = investments.filter((inv) => inv.status === 'active').length;\n return { totalInvested, currentValue, totalReturns, activeCount };\n }, [investments]);\n\n return (\n <PageLayout\n title=\"Investments\"\n description=\"Track your real estate investment portfolio and performance\"\n purpose=\"Follow every real-estate investment you hold in one portfolio — how much you put in, what it is worth now, the returns it has paid, and its risk level. Use it to judge performance and decide where to put your next dollar.\"\n actions={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n New Investment\n </button>\n }\n >\n {/* Portfolio Summary Stats — primary insight zone (one EmphasisPanel per page) */}\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Invested\"\n value={portfolioStats.totalInvested}\n prefix=\"$\"\n icon={<DollarSign className=\"h-4 w-4\" />}\n trend=\"up\"\n change={12}\n />\n <StatCard\n label=\"Current Value\"\n value={portfolioStats.currentValue}\n prefix=\"$\"\n icon={<TrendingUp className=\"h-4 w-4\" />}\n trend=\"up\"\n change={8}\n />\n <StatCard\n label=\"Total Returns\"\n value={portfolioStats.totalReturns}\n prefix=\"$\"\n icon={<PieChart className=\"h-4 w-4\" />}\n trend=\"up\"\n change={15}\n />\n <StatCard\n label=\"Active Investments\"\n value={portfolioStats.activeCount}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n <NextSteps\n storageKey=\"housingvista-investments\"\n steps={[\n {\n id: 'review-active',\n label: 'Review your active investments',\n description: 'Filter to the active deals currently paying returns.',\n onClick: () => setActiveTab('active'),\n },\n {\n id: 'find-funding',\n label: 'Find funding opportunities',\n description: 'See deals still raising capital that you can join.',\n onClick: () => setActiveTab('funding'),\n },\n {\n id: 'open-detail',\n label: 'Open an investment for full details',\n description: 'Drill into returns, investors, and risk for any holding.',\n onClick: () => {\n const first = investments[0];\n if (first) navigate(`/investments/${first.id}`);\n },\n },\n ]}\n />\n\n {/* Filter Tabs */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {statusTabs.map((tab) => (\n <button\n key={tab.value}\n onClick={() => setActiveTab(tab.value)}\n className={cn(\n 'rounded-md px-3 py-1.5 text-sm font-medium transition-colors',\n activeTab === tab.value\n ? 'bg-foreground text-background'\n : 'bg-bg-sunken text-text-secondary hover:text-text-primary'\n )}\n >\n {tab.label}\n </button>\n ))}\n </div>\n\n {/* Search */}\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder=\"Search by property or investment ID...\"\n />\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n Showing {filtered.length} of {investments.length} investments\n </p>\n\n {/* Investment Cards */}\n {filtered.length > 0 ? (\n <div className=\"space-y-4\">\n {filtered.map((investment) => {\n const totalInvested = investment.investors.reduce((s, i) => s + i.investmentAmount, 0);\n const totalReturns = investment.returns.reduce((s, r) => s + r.amount, 0);\n const currentValue = totalInvested + totalReturns;\n const returnPercentage = totalInvested > 0 ? (totalReturns / totalInvested) * 100 : 0;\n\n return (\n <div\n key={investment.id}\n className=\"cursor-pointer rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\"\n onClick={() => navigate(`/investments/${investment.id}`)}\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex-1\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2\">\n <Building className=\"h-5 w-5 text-text-secondary\" />\n </div>\n <div className=\"flex-1\">\n <div className=\"mb-2 flex flex-wrap items-center gap-2\">\n <h3 className=\"text-lg font-semibold text-text-primary\">{investment.property.title}</h3>\n <StatusBadge label={investment.status} variant={getStatusVariant(investment.status)} />\n <StatusBadge label={investment.investmentType.replace('_', ' ')} variant=\"muted\" />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 text-sm sm:grid-cols-2 lg:grid-cols-4\">\n <div>\n <p className=\"text-text-secondary\">Invested Amount</p>\n <p className=\"text-lg font-medium text-text-primary\">{formatCurrency(totalInvested)}</p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Current Value</p>\n <p className=\"text-lg font-medium text-text-primary\">{formatCurrency(currentValue)}</p>\n </div>\n <div>\n <p className=\"text-text-secondary\">Total Return</p>\n <div className={cn('font-medium', getReturnColor(totalReturns))}>\n <div className=\"flex items-center gap-1\">\n {totalReturns > 0 && <ArrowUpRight className=\"h-3 w-3\" />}\n {totalReturns < 0 && <ArrowDownRight className=\"h-3 w-3\" />}\n {formatCurrency(totalReturns)}\n </div>\n <div className=\"text-sm\">{returnPercentage.toFixed(1)}%</div>\n </div>\n </div>\n <div>\n <p className=\"text-text-secondary\">Risk Level</p>\n <p className=\"font-medium capitalize text-text-primary\">{investment.riskLevel}</p>\n </div>\n </div>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-5 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <TrendingUp className=\"h-3 w-3\" />\n Start: {formatDate(investment.startDate.toISOString())}\n </span>\n <span className=\"flex items-center gap-1\">\n <Target className=\"h-3 w-3\" />\n Type: {investment.investmentType.replace('_', ' ')}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Returns: {investment.returns.length} payments\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"flex shrink-0 gap-2\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`/investments/${investment.id}`);\n }}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Eye className=\"h-4 w-4\" />\n View Details\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<PieChart className=\"h-12 w-12\" />}\n title=\"No investments found\"\n description=\"Try adjusting your search criteria or explore new investment opportunities.\"\n action={\n <button className=\"inline-flex items-center gap-2 rounded-md bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors hover:bg-foreground/90\">\n <Plus className=\"h-4 w-4\" />\n Explore Investments\n </button>\n }\n />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -8,9 +8,9 @@ import { useMemo as a, useState as o } from "react";
|
|
|
8
8
|
import { BookOpen as s, ClipboardCheck as c, Clock as l, FileText as u, Gavel as d, Scale as f, Search as p, Shield as m, Star as h } from "lucide-react";
|
|
9
9
|
import { useNavigate as g } from "react-router-dom";
|
|
10
10
|
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
11
|
-
import { EmphasisPanel as y } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { EmphasisPanel as y, NextSteps as b } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/housingvista/pages/legal/LegalPage.tsx
|
|
13
|
-
var
|
|
13
|
+
var x = [
|
|
14
14
|
{
|
|
15
15
|
value: "all",
|
|
16
16
|
label: "All"
|
|
@@ -36,7 +36,7 @@ var b = [
|
|
|
36
36
|
label: "Document Review"
|
|
37
37
|
}
|
|
38
38
|
];
|
|
39
|
-
function
|
|
39
|
+
function S(e) {
|
|
40
40
|
switch (e) {
|
|
41
41
|
case "title_search": return /* @__PURE__ */ _(p, { className: "h-4 w-4" });
|
|
42
42
|
case "lease_review": return /* @__PURE__ */ _(u, { className: "h-4 w-4" });
|
|
@@ -46,7 +46,7 @@ function x(e) {
|
|
|
46
46
|
default: return /* @__PURE__ */ _(f, { className: "h-4 w-4" });
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
function
|
|
49
|
+
function C(e) {
|
|
50
50
|
switch (e) {
|
|
51
51
|
case "available": return "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400";
|
|
52
52
|
case "busy": return "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400";
|
|
@@ -54,13 +54,13 @@ function S(e) {
|
|
|
54
54
|
default: return "bg-bg-sunken text-text-secondary";
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function w({ rating: t }) {
|
|
58
58
|
return /* @__PURE__ */ _("div", {
|
|
59
59
|
className: "flex items-center gap-0.5",
|
|
60
60
|
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */ _(h, { className: e("h-3 w-3", r < Math.floor(t) ? "fill-amber-400 text-amber-400" : "text-text-secondary/30") }, r))
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
function
|
|
63
|
+
function T({ service: t }) {
|
|
64
64
|
let n = g(), [r, i] = o(!1);
|
|
65
65
|
return /* @__PURE__ */ v("div", {
|
|
66
66
|
className: "rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md",
|
|
@@ -71,7 +71,7 @@ function w({ service: t }) {
|
|
|
71
71
|
className: "flex items-start gap-3",
|
|
72
72
|
children: [/* @__PURE__ */ _("div", {
|
|
73
73
|
className: "flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary",
|
|
74
|
-
children:
|
|
74
|
+
children: S(t.category)
|
|
75
75
|
}), /* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h3", {
|
|
76
76
|
className: "text-sm font-semibold text-text-primary",
|
|
77
77
|
children: t.title
|
|
@@ -80,7 +80,7 @@ function w({ service: t }) {
|
|
|
80
80
|
children: t.providerName
|
|
81
81
|
})] })]
|
|
82
82
|
}), /* @__PURE__ */ _("span", {
|
|
83
|
-
className: e("inline-flex rounded-full px-2 py-0.5 text-xs font-medium capitalize",
|
|
83
|
+
className: e("inline-flex rounded-full px-2 py-0.5 text-xs font-medium capitalize", C(t.status)),
|
|
84
84
|
children: t.status
|
|
85
85
|
})]
|
|
86
86
|
}),
|
|
@@ -92,7 +92,7 @@ function w({ service: t }) {
|
|
|
92
92
|
className: "mb-3 flex items-center gap-4 text-xs",
|
|
93
93
|
children: [/* @__PURE__ */ v("div", {
|
|
94
94
|
className: "flex items-center gap-1.5",
|
|
95
|
-
children: [/* @__PURE__ */ _(
|
|
95
|
+
children: [/* @__PURE__ */ _(w, { rating: t.providerRating }), /* @__PURE__ */ _("span", {
|
|
96
96
|
className: "font-medium text-text-primary",
|
|
97
97
|
children: t.providerRating
|
|
98
98
|
})]
|
|
@@ -142,7 +142,7 @@ function w({ service: t }) {
|
|
|
142
142
|
]
|
|
143
143
|
});
|
|
144
144
|
}
|
|
145
|
-
function
|
|
145
|
+
function E() {
|
|
146
146
|
let { data: s } = i(), [c, l] = o("all"), [u, d] = o(""), p = a(() => s.filter((e) => {
|
|
147
147
|
let t = c === "all" || e.category === c, n = !u || e.title.toLowerCase().includes(u.toLowerCase()) || e.providerName.toLowerCase().includes(u.toLowerCase()) || e.description.toLowerCase().includes(u.toLowerCase());
|
|
148
148
|
return t && n;
|
|
@@ -162,7 +162,7 @@ function T() {
|
|
|
162
162
|
children: [
|
|
163
163
|
/* @__PURE__ */ _(y, {
|
|
164
164
|
className: "flex flex-wrap items-center gap-2 p-3",
|
|
165
|
-
children:
|
|
165
|
+
children: x.map((t) => /* @__PURE__ */ _("button", {
|
|
166
166
|
onClick: () => l(t.value),
|
|
167
167
|
className: e("rounded-full px-4 py-1.5 text-sm font-medium transition-colors", c === t.value ? "bg-foreground text-background" : "border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
|
|
168
168
|
children: t.label
|
|
@@ -173,9 +173,32 @@ function T() {
|
|
|
173
173
|
onSearchChange: d,
|
|
174
174
|
searchPlaceholder: "Search legal services..."
|
|
175
175
|
}),
|
|
176
|
+
/* @__PURE__ */ _(b, {
|
|
177
|
+
storageKey: "housingvista-legal",
|
|
178
|
+
steps: [
|
|
179
|
+
{
|
|
180
|
+
id: "verify-title",
|
|
181
|
+
label: "Verify a property title",
|
|
182
|
+
description: "The first legal check before any purchase — find a title-search expert.",
|
|
183
|
+
onClick: () => l("title_search")
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "rent-agreement",
|
|
187
|
+
label: "Draft or review a rent agreement",
|
|
188
|
+
description: "Get a lawyer to prepare or vet your lease paperwork.",
|
|
189
|
+
onClick: () => l("lease_review")
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: "registration",
|
|
193
|
+
label: "Handle property registration",
|
|
194
|
+
description: "Find help to register a deal with the authorities.",
|
|
195
|
+
onClick: () => l("registration")
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
}),
|
|
176
199
|
p.length > 0 ? /* @__PURE__ */ _("div", {
|
|
177
200
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
178
|
-
children: p.map((e) => /* @__PURE__ */ _(
|
|
201
|
+
children: p.map((e) => /* @__PURE__ */ _(T, { service: e }, e.id))
|
|
179
202
|
}) : /* @__PURE__ */ _(n, {
|
|
180
203
|
illustration: "empty-search",
|
|
181
204
|
icon: /* @__PURE__ */ _(f, { className: "h-12 w-12" }),
|
|
@@ -186,6 +209,6 @@ function T() {
|
|
|
186
209
|
});
|
|
187
210
|
}
|
|
188
211
|
//#endregion
|
|
189
|
-
export {
|
|
212
|
+
export { E as LegalPage };
|
|
190
213
|
|
|
191
214
|
//# sourceMappingURL=LegalPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegalPage.js","names":[],"sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"sourcesContent":["import { useState, 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 ExternalLink,\n} from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState } from '../../components';\nimport { useLegalServices } from '../../hooks';\nimport { cn } from '../../utils/helpers';\nimport type { LegalService } from '../../types';\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-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400';\n case 'busy':\n return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400';\n case 'unavailable':\n return 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400';\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\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'h-3 w-3',\n i < Math.floor(rating) ? 'fill-amber-400 text-amber-400' : 'text-text-secondary/30'\n )}\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=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\">\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 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary\">\n {getCategoryIcon(service.category)}\n </div>\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary\">{service.title}</h3>\n <p className=\"text-xs text-text-secondary\">{service.providerName}</p>\n </div>\n </div>\n <span\n className={cn(\n 'inline-flex 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-3 text-xs text-text-secondary line-clamp-2\">{service.description}</p>\n\n {/* Rating + Duration */}\n <div className=\"mb-3 flex items-center gap-4 text-xs\">\n <div className=\"flex items-center gap-1.5\">\n <RatingStars rating={service.providerRating} />\n <span className=\"font-medium text-text-primary\">{service.providerRating}</span>\n </div>\n <span className=\"flex items-center gap-1 text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n {service.estimatedDuration}\n </span>\n </div>\n\n {/* Price + Consultation Fee */}\n <div className=\"mb-3 flex items-center justify-between text-xs\">\n <span className=\"font-semibold text-text-primary\">{service.priceRange}</span>\n {service.consultationFee > 0 ? (\n <span className=\"text-text-secondary\">Consultation: ${service.consultationFee}</span>\n ) : (\n <span className=\"text-emerald-600 dark:text-emerald-400\">Free Consultation</span>\n )}\n </div>\n\n {/* Documents Required (collapsible) */}\n {service.documentsRequired.length > 0 && (\n <div className=\"mb-3\">\n <button\n onClick={() => setShowDocs(!showDocs)}\n className=\"text-xs font-medium text-text-secondary hover:text-text-primary\"\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 rounded-full bg-muted-foreground\" />\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 '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-foreground text-background hover:bg-foreground/90'\n )}\n >\n {service.status === 'unavailable' ? 'Currently Unavailable' : 'Book Now'}\n </button>\n </div>\n );\n}\n\nexport function LegalPage() {\n const { data: services } = useLegalServices();\n const [activeCategory, setActiveCategory] = useState<CategoryTab>('all');\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n return services.filter((s) => {\n const matchesCategory = activeCategory === 'all' || s.category === activeCategory;\n const matchesSearch =\n !search ||\n s.title.toLowerCase().includes(search.toLowerCase()) ||\n s.providerName.toLowerCase().includes(search.toLowerCase()) ||\n s.description.toLowerCase().includes(search.toLowerCase());\n return matchesCategory && matchesSearch;\n });\n }, [services, activeCategory, search]);\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-emerald-600 dark:text-emerald-400\">\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-foreground text-background'\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 {/* Service Cards */}\n {filtered.length > 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((service) => (\n <LegalServiceCard key={service.id} service={service} />\n ))}\n </div>\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":";;;;;;;;;;;;AAsBA,IAAM,IAAwD;CAC5D;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAgB,OAAO;EAAsB;CACtD;EAAE,OAAO;EAAgB,OAAO;EAAkB;CAClD;EAAE,OAAO;EAAsB,OAAO;EAAY;CAClD;EAAE,OAAO;EAAgB,OAAO;EAAgB;CAChD;EAAE,OAAO;EAAc,OAAO;EAAmB;CAClD;AAED,SAAS,EAAgB,GAAkB;AACzC,SAAQ,GAAR;EACE,KAAK,eACH,QAAO,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;EAC3C,KAAK,eACH,QAAO,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EACzC,KAAK,qBACH,QAAO,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;EACtC,KAAK,eACH,QAAO,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;EAC/C,KAAK,aACH,QAAO,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EACzC,QACE,QAAO,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;;;AAI1C,SAAS,EAAgB,GAAgB;AACvC,SAAQ,GAAR;EACE,KAAK,YACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,KAAK,cACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAS,EAAY,EAAE,aAA8B;AACnD,QACE,kBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,GAAD,EAEE,WAAW,EACT,WACA,IAAI,KAAK,MAAM,EAAO,GAAG,kCAAkC,yBAC5D,EACD,EALK,EAKL,CACF;EACE,CAAA;;AAIV,SAAS,EAAiB,EAAE,cAAsC;CAChE,IAAM,IAAW,GAAa,EACxB,CAAC,GAAU,KAAe,EAAS,GAAM;AAE/C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAgB,EAAQ,SAAS;MAC9B,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA2C,EAAQ;MAAW,CAAA,EAC5E,kBAAC,KAAD;MAAG,WAAU;gBAA+B,EAAQ;MAAiB,CAAA,CACjE,EAAA,CAAA,CACF;QACN,kBAAC,QAAD;KACE,WAAW,EACT,uEACA,EAAgB,EAAQ,OAAO,CAChC;eAEA,EAAQ;KACJ,CAAA,CACH;;GAGN,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAQ;IAAgB,CAAA;GAGtF,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAQ,gBAAkB,CAAA,EAC/C,kBAAC,QAAD;MAAM,WAAU;gBAAiC,EAAQ;MAAsB,CAAA,CAC3E;QACN,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC5B,EAAQ,kBACJ;OACH;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAmC,EAAQ;KAAkB,CAAA,EAC5E,EAAQ,kBAAkB,IACzB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAsC,mBAAgB,EAAQ,gBAAuB;SAErF,kBAAC,QAAD;KAAM,WAAU;eAAyC;KAAwB,CAAA,CAE/E;;GAGL,EAAQ,kBAAkB,SAAS,KAClC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,eAAe,EAAY,CAAC,EAAS;KACrC,WAAU;eAFZ;MAIG,IAAW,SAAS;MAAO;MAAsB,EAAQ,kBAAkB;MAAO;MAC5E;QACR,KACC,kBAAC,MAAD;KAAI,WAAU;eACX,EAAQ,kBAAkB,KAAK,GAAK,MACnC,kBAAC,MAAD;MAAY,WAAU;gBAAtB,CACE,kBAAC,QAAD,EAAM,WAAU,4CAA6C,CAAA,EAC5D,EACE;QAHI,EAGJ,CACL;KACC,CAAA,CAEH;;GAIR,kBAAC,UAAD;IACE,eAAe,EAAS,UAAU,EAAQ,KAAK;IAC/C,UAAU,EAAQ,WAAW;IAC7B,WAAW,EACT,qEACA,EAAQ,WAAW,gBACf,wDACA,uDACL;cAEA,EAAQ,WAAW,gBAAgB,0BAA0B;IACvD,CAAA;GACL;;;AAIV,SAAgB,IAAY;CAC1B,IAAM,EAAE,MAAM,MAAa,GAAkB,EACvC,CAAC,GAAgB,KAAqB,EAAsB,MAAM,EAClE,CAAC,GAAQ,KAAa,EAAS,GAAG,EAElC,IAAW,QACR,EAAS,QAAQ,MAAM;EAC5B,IAAM,IAAkB,MAAmB,SAAS,EAAE,aAAa,GAC7D,IACJ,CAAC,KACD,EAAE,MAAM,aAAa,CAAC,SAAS,EAAO,aAAa,CAAC,IACpD,EAAE,aAAa,aAAa,CAAC,SAAS,EAAO,aAAa,CAAC,IAC3D,EAAE,YAAY,aAAa,CAAC,SAAS,EAAO,aAAa,CAAC;AAC5D,SAAO,KAAmB;GAC1B,EACD;EAAC;EAAU;EAAgB;EAAO,CAAC;AAEtC,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SAAQ;EACR,SACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA,EAAA,oBAE7B;;YARX;GAYE,kBAAC,GAAD;IAAe,WAAU;cACtB,EAAa,KAAK,MACjB,kBAAC,UAAD;KAEE,eAAe,EAAkB,EAAI,MAAM;KAC3C,WAAW,EACT,kEACA,MAAmB,EAAI,QACnB,kCACA,2GACL;eAEA,EAAI;KACE,EAVF,EAAI,MAUF,CACT;IACY,CAAA;GAGhB,kBAAC,GAAD;IAAW,aAAa;IAAQ,gBAAgB;IAAW,mBAAkB;IAA6B,CAAA;GAGzG,EAAS,SAAS,IACjB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MACb,kBAAC,GAAD,EAA4C,YAAW,EAAhC,EAAQ,GAAwB,CACvD;IACE,CAAA,GAEN,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;IACrC,OAAM;IACN,aAAY;IACZ,CAAA;GAEO"}
|
|
1
|
+
{"version":3,"file":"LegalPage.js","names":[],"sources":["../../../../src/housingvista/pages/legal/LegalPage.tsx"],"sourcesContent":["import { useState, 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 ExternalLink,\n} from 'lucide-react';\nimport { EmphasisPanel, NextSteps } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, FilterBar, EmptyState } from '../../components';\nimport { useLegalServices } from '../../hooks';\nimport { cn } from '../../utils/helpers';\nimport type { LegalService } from '../../types';\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-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400';\n case 'busy':\n return 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400';\n case 'unavailable':\n return 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400';\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\">\n {Array.from({ length: 5 }).map((_, i) => (\n <Star\n key={i}\n className={cn(\n 'h-3 w-3',\n i < Math.floor(rating) ? 'fill-amber-400 text-amber-400' : 'text-text-secondary/30'\n )}\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=\"rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md\">\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 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary\">\n {getCategoryIcon(service.category)}\n </div>\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary\">{service.title}</h3>\n <p className=\"text-xs text-text-secondary\">{service.providerName}</p>\n </div>\n </div>\n <span\n className={cn(\n 'inline-flex 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-3 text-xs text-text-secondary line-clamp-2\">{service.description}</p>\n\n {/* Rating + Duration */}\n <div className=\"mb-3 flex items-center gap-4 text-xs\">\n <div className=\"flex items-center gap-1.5\">\n <RatingStars rating={service.providerRating} />\n <span className=\"font-medium text-text-primary\">{service.providerRating}</span>\n </div>\n <span className=\"flex items-center gap-1 text-text-secondary\">\n <Clock className=\"h-3 w-3\" />\n {service.estimatedDuration}\n </span>\n </div>\n\n {/* Price + Consultation Fee */}\n <div className=\"mb-3 flex items-center justify-between text-xs\">\n <span className=\"font-semibold text-text-primary\">{service.priceRange}</span>\n {service.consultationFee > 0 ? (\n <span className=\"text-text-secondary\">Consultation: ${service.consultationFee}</span>\n ) : (\n <span className=\"text-emerald-600 dark:text-emerald-400\">Free Consultation</span>\n )}\n </div>\n\n {/* Documents Required (collapsible) */}\n {service.documentsRequired.length > 0 && (\n <div className=\"mb-3\">\n <button\n onClick={() => setShowDocs(!showDocs)}\n className=\"text-xs font-medium text-text-secondary hover:text-text-primary\"\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 rounded-full bg-muted-foreground\" />\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 '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-foreground text-background hover:bg-foreground/90'\n )}\n >\n {service.status === 'unavailable' ? 'Currently Unavailable' : 'Book Now'}\n </button>\n </div>\n );\n}\n\nexport function LegalPage() {\n const { data: services } = useLegalServices();\n const [activeCategory, setActiveCategory] = useState<CategoryTab>('all');\n const [search, setSearch] = useState('');\n\n const filtered = useMemo(() => {\n return services.filter((s) => {\n const matchesCategory = activeCategory === 'all' || s.category === activeCategory;\n const matchesSearch =\n !search ||\n s.title.toLowerCase().includes(search.toLowerCase()) ||\n s.providerName.toLowerCase().includes(search.toLowerCase()) ||\n s.description.toLowerCase().includes(search.toLowerCase());\n return matchesCategory && matchesSearch;\n });\n }, [services, activeCategory, search]);\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-emerald-600 dark:text-emerald-400\">\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-foreground text-background'\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 {filtered.length > 0 ? (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((service) => (\n <LegalServiceCard key={service.id} service={service} />\n ))}\n </div>\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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"}
|
|
@@ -8,9 +8,9 @@ import { useMemo as a, useState as o } from "react";
|
|
|
8
8
|
import { CheckCircle as s, Clock as c, MapPin as l, MessageCircle as u, Paintbrush as d, Phone as f, Shield as p, ShieldCheck as m, SprayCanIcon as h, Star as g, Trees as _, Wrench as v, Zap as y } from "lucide-react";
|
|
9
9
|
import { useNavigate as b } from "react-router-dom";
|
|
10
10
|
import { jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
11
|
-
import { EmphasisPanel as C } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { EmphasisPanel as C, NextSteps as w } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/housingvista/pages/marketplace/MarketplacePage.tsx
|
|
13
|
-
var
|
|
13
|
+
var T = [
|
|
14
14
|
{
|
|
15
15
|
value: "all",
|
|
16
16
|
label: "All"
|
|
@@ -40,7 +40,7 @@ var w = [
|
|
|
40
40
|
label: "Landscaping"
|
|
41
41
|
}
|
|
42
42
|
];
|
|
43
|
-
function
|
|
43
|
+
function E(e) {
|
|
44
44
|
switch (e) {
|
|
45
45
|
case "repair": return /* @__PURE__ */ x(v, { className: "h-4 w-4" });
|
|
46
46
|
case "maintenance": return /* @__PURE__ */ x(y, { className: "h-4 w-4" });
|
|
@@ -52,13 +52,13 @@ function T(e) {
|
|
|
52
52
|
default: return /* @__PURE__ */ x(v, { className: "h-4 w-4" });
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function D({ rating: t }) {
|
|
56
56
|
return /* @__PURE__ */ x("div", {
|
|
57
57
|
className: "flex items-center gap-0.5",
|
|
58
58
|
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */ x(g, { className: e("h-3.5 w-3.5", r < Math.floor(t) ? "fill-amber-400 text-amber-400" : r < t ? "fill-amber-400/50 text-amber-400" : "text-text-secondary/30") }, r))
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
-
function
|
|
61
|
+
function O({ service: e }) {
|
|
62
62
|
let t = b();
|
|
63
63
|
return /* @__PURE__ */ S("div", {
|
|
64
64
|
className: "rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-sm transition-shadow hover:shadow-md",
|
|
@@ -87,13 +87,13 @@ function D({ service: e }) {
|
|
|
87
87
|
})]
|
|
88
88
|
}), /* @__PURE__ */ S("span", {
|
|
89
89
|
className: "inline-flex items-center gap-1 rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary capitalize",
|
|
90
|
-
children: [
|
|
90
|
+
children: [E(e.category), e.category.replace("_", " ")]
|
|
91
91
|
})]
|
|
92
92
|
}),
|
|
93
93
|
/* @__PURE__ */ S("div", {
|
|
94
94
|
className: "mb-3 flex items-center gap-2",
|
|
95
95
|
children: [
|
|
96
|
-
/* @__PURE__ */ x(
|
|
96
|
+
/* @__PURE__ */ x(D, { rating: e.rating }),
|
|
97
97
|
/* @__PURE__ */ x("span", {
|
|
98
98
|
className: "text-xs font-medium text-text-primary",
|
|
99
99
|
children: e.rating
|
|
@@ -151,7 +151,7 @@ function D({ service: e }) {
|
|
|
151
151
|
]
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
|
-
function
|
|
154
|
+
function k() {
|
|
155
155
|
let { data: s } = i(), [c, l] = o(""), [u, d] = o("all"), f = a(() => s.filter((e) => {
|
|
156
156
|
let t = !c || e.name.toLowerCase().includes(c.toLowerCase()) || e.providerName.toLowerCase().includes(c.toLowerCase()) || e.description.toLowerCase().includes(c.toLowerCase()), n = u === "all" || e.category === u;
|
|
157
157
|
return t && n;
|
|
@@ -167,7 +167,7 @@ function O() {
|
|
|
167
167
|
children: [
|
|
168
168
|
/* @__PURE__ */ x(C, {
|
|
169
169
|
className: "flex flex-wrap items-center gap-2 p-3",
|
|
170
|
-
children:
|
|
170
|
+
children: T.map((t) => /* @__PURE__ */ x("button", {
|
|
171
171
|
onClick: () => d(t.value),
|
|
172
172
|
className: e("rounded-full px-4 py-1.5 text-sm font-medium transition-colors", u === t.value ? "bg-foreground text-background" : "border border-border-subtle bg-bg-surface text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
|
|
173
173
|
children: t.label
|
|
@@ -178,6 +178,31 @@ function O() {
|
|
|
178
178
|
onSearchChange: l,
|
|
179
179
|
searchPlaceholder: "Search services, providers..."
|
|
180
180
|
}),
|
|
181
|
+
/* @__PURE__ */ x(w, {
|
|
182
|
+
storageKey: "housingvista-marketplace",
|
|
183
|
+
steps: [
|
|
184
|
+
{
|
|
185
|
+
id: "book-repair",
|
|
186
|
+
label: "Fix something now",
|
|
187
|
+
description: "Find a plumber or repair pro for an urgent issue.",
|
|
188
|
+
onClick: () => d("repair")
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "book-cleaning",
|
|
192
|
+
label: "Schedule a cleaning",
|
|
193
|
+
description: "Get a property move-in or turnover ready.",
|
|
194
|
+
onClick: () => d("cleaning")
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "see-featured",
|
|
198
|
+
label: "Browse all providers",
|
|
199
|
+
description: "Clear filters to compare every vetted professional.",
|
|
200
|
+
onClick: () => {
|
|
201
|
+
d("all"), l("");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}),
|
|
181
206
|
/* @__PURE__ */ S("p", {
|
|
182
207
|
className: "text-sm text-text-secondary",
|
|
183
208
|
children: [
|
|
@@ -189,7 +214,7 @@ function O() {
|
|
|
189
214
|
}),
|
|
190
215
|
f.length > 0 ? /* @__PURE__ */ x("div", {
|
|
191
216
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
192
|
-
children: f.map((e) => /* @__PURE__ */ x(
|
|
217
|
+
children: f.map((e) => /* @__PURE__ */ x(O, { service: e }, e.id))
|
|
193
218
|
}) : /* @__PURE__ */ x(n, {
|
|
194
219
|
illustration: "empty-search",
|
|
195
220
|
title: "No services found",
|
|
@@ -199,6 +224,6 @@ function O() {
|
|
|
199
224
|
});
|
|
200
225
|
}
|
|
201
226
|
//#endregion
|
|
202
|
-
export {
|
|
227
|
+
export { k as MarketplacePage };
|
|
203
228
|
|
|
204
229
|
//# sourceMappingURL=MarketplacePage.js.map
|