@burdenoff/microfe-housingvista 2026.703.2 → 2026.703.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/housingvista/HousingVistaRoot.js +23 -23
- package/dist/housingvista/HousingVistaRoot.js.map +1 -1
- package/dist/housingvista/HousingVistaRoutes.js +37 -17
- package/dist/housingvista/HousingVistaRoutes.js.map +1 -1
- package/dist/housingvista/components/Breadcrumbs.js +32 -27
- package/dist/housingvista/components/Breadcrumbs.js.map +1 -1
- package/dist/housingvista/components/ContactOwnerDialog.js +104 -0
- package/dist/housingvista/components/ContactOwnerDialog.js.map +1 -0
- package/dist/housingvista/components/FileUploader.js +72 -0
- package/dist/housingvista/components/FileUploader.js.map +1 -0
- package/dist/housingvista/components/ReviewsSection.js +131 -0
- package/dist/housingvista/components/ReviewsSection.js.map +1 -0
- package/dist/housingvista/components/index.js +3 -0
- package/dist/housingvista/hooks/index.js +768 -223
- package/dist/housingvista/hooks/index.js.map +1 -1
- package/dist/housingvista/hooks/platform.js +228 -0
- package/dist/housingvista/hooks/platform.js.map +1 -0
- package/dist/housingvista/pages/HomePage.js +126 -134
- package/dist/housingvista/pages/HomePage.js.map +1 -1
- package/dist/housingvista/pages/amc/AMCManagementPage.js +209 -170
- package/dist/housingvista/pages/amc/AMCManagementPage.js.map +1 -1
- package/dist/housingvista/pages/analytics/AnalyticsPage.js +123 -145
- package/dist/housingvista/pages/analytics/AnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/assets/AssetDetailPage.js +287 -0
- package/dist/housingvista/pages/assets/AssetDetailPage.js.map +1 -0
- package/dist/housingvista/pages/assets/AssetManagementPage.js +407 -109
- package/dist/housingvista/pages/assets/AssetManagementPage.js.map +1 -1
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js +350 -0
- package/dist/housingvista/pages/finance/HomeLoanDetailPage.js.map +1 -0
- package/dist/housingvista/pages/finance/HomeLoansPage.js +160 -132
- package/dist/housingvista/pages/finance/HomeLoansPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js +615 -400
- package/dist/housingvista/pages/investments/InvestmentDetailPage.js.map +1 -1
- package/dist/housingvista/pages/investments/InvestmentsPage.js +210 -191
- package/dist/housingvista/pages/investments/InvestmentsPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalPage.js +94 -74
- package/dist/housingvista/pages/legal/LegalPage.js.map +1 -1
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js +301 -0
- package/dist/housingvista/pages/legal/LegalServiceDetailPage.js.map +1 -0
- package/dist/housingvista/pages/market/AreaInsightsPage.js +95 -86
- package/dist/housingvista/pages/market/AreaInsightsPage.js.map +1 -1
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js +73 -64
- package/dist/housingvista/pages/market/MarketAnalyticsPage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/MarketplacePage.js +102 -81
- package/dist/housingvista/pages/marketplace/MarketplacePage.js.map +1 -1
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js +317 -0
- package/dist/housingvista/pages/marketplace/ServiceDetailPage.js.map +1 -0
- package/dist/housingvista/pages/offers/OffersPage.js +306 -0
- package/dist/housingvista/pages/offers/OffersPage.js.map +1 -0
- package/dist/housingvista/pages/properties/AddPropertyPage.js +240 -225
- package/dist/housingvista/pages/properties/AddPropertyPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertiesPage.js +178 -204
- package/dist/housingvista/pages/properties/PropertiesPage.js.map +1 -1
- package/dist/housingvista/pages/properties/PropertyDetailPage.js +648 -385
- package/dist/housingvista/pages/properties/PropertyDetailPage.js.map +1 -1
- package/dist/housingvista/pages/rental/RentalManagementPage.js +552 -309
- package/dist/housingvista/pages/rental/RentalManagementPage.js.map +1 -1
- package/dist/housingvista/pages/settings/ProfilePage.js +81 -144
- package/dist/housingvista/pages/settings/ProfilePage.js.map +1 -1
- package/dist/housingvista/pages/settings/SettingsPage.js +80 -143
- package/dist/housingvista/pages/settings/SettingsPage.js.map +1 -1
- package/dist/housingvista/pages/shortlist/ShortlistPage.js +32 -19
- package/dist/housingvista/pages/shortlist/ShortlistPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js +103 -124
- package/dist/housingvista/pages/transactions/TransactionDetailPage.js.map +1 -1
- package/dist/housingvista/pages/transactions/TransactionsPage.js +205 -201
- package/dist/housingvista/pages/transactions/TransactionsPage.js.map +1 -1
- package/dist/housingvista/pages/verification/VerificationDetailPage.js +352 -0
- package/dist/housingvista/pages/verification/VerificationDetailPage.js.map +1 -0
- package/dist/housingvista/pages/verification/VerificationPage.js +224 -97
- package/dist/housingvista/pages/verification/VerificationPage.js.map +1 -1
- package/dist/housingvista/pages/viewings/ViewingsPage.js +259 -0
- package/dist/housingvista/pages/viewings/ViewingsPage.js.map +1 -0
- package/dist/housingvista/utils/helpers.js +11 -1
- package/dist/housingvista/utils/helpers.js.map +1 -1
- package/package.json +1 -1
- package/dist/housingvista/pages/chat/ChatPage.js +0 -28
- package/dist/housingvista/pages/chat/ChatPage.js.map +0 -1
- package/dist/housingvista/pages/notifications/NotificationsPage.js +0 -28
- package/dist/housingvista/pages/notifications/NotificationsPage.js.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { cn as e } from "../utils/helpers.js";
|
|
2
|
+
import { useFileUpload as t } from "../hooks/platform.js";
|
|
3
|
+
import "../hooks/index.js";
|
|
4
|
+
import { notify as n } from "../lib/toast.js";
|
|
5
|
+
import { useRef as r, useState as i } from "react";
|
|
6
|
+
import { FileText as a, Loader2 as o, Upload as s, X as c } from "lucide-react";
|
|
7
|
+
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
8
|
+
//#region src/housingvista/components/FileUploader.tsx
|
|
9
|
+
var d = ({ onUploaded: d, value: f = [], onRemove: p, accept: m, multiple: h = !0, label: g = "Choose files", className: _ }) => {
|
|
10
|
+
let { uploadFile: v } = t(), y = r(null), [b, x] = i(!1), S = async (e) => {
|
|
11
|
+
if (!e || e.length === 0) return;
|
|
12
|
+
x(!0);
|
|
13
|
+
let t = [];
|
|
14
|
+
try {
|
|
15
|
+
for (let n of Array.from(e)) {
|
|
16
|
+
let e = await v(n);
|
|
17
|
+
t.push(e);
|
|
18
|
+
}
|
|
19
|
+
t.length > 0 && (d(t), n.success(`${t.length} file${t.length > 1 ? "s" : ""} uploaded`));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
n.error("Upload failed", e.message);
|
|
22
|
+
} finally {
|
|
23
|
+
x(!1), y.current && (y.current.value = "");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return /* @__PURE__ */ u("div", {
|
|
27
|
+
className: e("space-y-3", _),
|
|
28
|
+
children: [
|
|
29
|
+
/* @__PURE__ */ u("button", {
|
|
30
|
+
type: "button",
|
|
31
|
+
disabled: b,
|
|
32
|
+
onClick: () => y.current?.click(),
|
|
33
|
+
className: "flex w-full items-center justify-center gap-2 rounded-md border border-dashed border-border-subtle bg-bg-sunken px-4 py-6 text-sm font-medium text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary disabled:opacity-60",
|
|
34
|
+
children: [b ? /* @__PURE__ */ l(o, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ l(s, { className: "h-4 w-4" }), b ? "Uploading…" : g]
|
|
35
|
+
}),
|
|
36
|
+
/* @__PURE__ */ l("input", {
|
|
37
|
+
ref: y,
|
|
38
|
+
type: "file",
|
|
39
|
+
accept: m,
|
|
40
|
+
multiple: h,
|
|
41
|
+
className: "hidden",
|
|
42
|
+
onChange: (e) => S(e.target.files)
|
|
43
|
+
}),
|
|
44
|
+
f.length > 0 && /* @__PURE__ */ l("ul", {
|
|
45
|
+
className: "space-y-1.5",
|
|
46
|
+
children: f.map((e) => /* @__PURE__ */ u("li", {
|
|
47
|
+
className: "flex items-center justify-between gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm",
|
|
48
|
+
children: [/* @__PURE__ */ u("a", {
|
|
49
|
+
href: e.url,
|
|
50
|
+
target: "_blank",
|
|
51
|
+
rel: "noopener noreferrer",
|
|
52
|
+
className: "flex min-w-0 items-center gap-2 text-text-primary hover:underline",
|
|
53
|
+
children: [/* @__PURE__ */ l(a, { className: "h-4 w-4 shrink-0 text-text-secondary" }), /* @__PURE__ */ l("span", {
|
|
54
|
+
className: "truncate",
|
|
55
|
+
children: e.name
|
|
56
|
+
})]
|
|
57
|
+
}), p && /* @__PURE__ */ l("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
onClick: () => p(e.id),
|
|
60
|
+
"aria-label": `Remove ${e.name}`,
|
|
61
|
+
className: "shrink-0 text-text-secondary hover:text-status-error-text",
|
|
62
|
+
children: /* @__PURE__ */ l(c, { className: "h-4 w-4" })
|
|
63
|
+
})]
|
|
64
|
+
}, e.id))
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
export { d as FileUploader };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=FileUploader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FileUploader.js","names":[],"sources":["../../../src/housingvista/components/FileUploader.tsx"],"sourcesContent":["/**\n * Real file upload widget backed by wspace-files-svc (presigned initiate → PUT\n * → complete). Emits durable File ids + download URLs via `onUploaded`. Replaces\n * every dead \"Choose Files\" / \"Upload Document\" button in the MFE.\n * @module housingvista\n */\n\nimport { useRef, useState, type FC } from 'react';\nimport { Upload, X, FileText, Loader2 } from 'lucide-react';\nimport { useFileUpload, type HvUploadedFile } from '../hooks';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/helpers';\n\ninterface FileUploaderProps {\n onUploaded: (files: HvUploadedFile[]) => void;\n /** Files already attached (rendered as removable chips). */\n value?: HvUploadedFile[];\n onRemove?: (fileId: string) => void;\n accept?: string;\n multiple?: boolean;\n label?: string;\n className?: string;\n}\n\nexport const FileUploader: FC<FileUploaderProps> = ({\n onUploaded,\n value = [],\n onRemove,\n accept,\n multiple = true,\n label = 'Choose files',\n className,\n}) => {\n const { uploadFile } = useFileUpload();\n const inputRef = useRef<HTMLInputElement>(null);\n const [busy, setBusy] = useState(false);\n\n const handleFiles = async (fileList: FileList | null) => {\n if (!fileList || fileList.length === 0) return;\n setBusy(true);\n const uploaded: HvUploadedFile[] = [];\n try {\n for (const file of Array.from(fileList)) {\n const result = await uploadFile(file);\n uploaded.push(result);\n }\n if (uploaded.length > 0) {\n onUploaded(uploaded);\n notify.success(`${uploaded.length} file${uploaded.length > 1 ? 's' : ''} uploaded`);\n }\n } catch (err) {\n notify.error('Upload failed', (err as Error).message);\n } finally {\n setBusy(false);\n if (inputRef.current) inputRef.current.value = '';\n }\n };\n\n return (\n <div className={cn('space-y-3', className)}>\n <button\n type=\"button\"\n disabled={busy}\n onClick={() => inputRef.current?.click()}\n className=\"flex w-full items-center justify-center gap-2 rounded-md border border-dashed border-border-subtle bg-bg-sunken px-4 py-6 text-sm font-medium text-text-secondary transition-colors hover:bg-bg-surface hover:text-text-primary disabled:opacity-60\"\n >\n {busy ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Upload className=\"h-4 w-4\" />}\n {busy ? 'Uploading…' : label}\n </button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n className=\"hidden\"\n onChange={(event) => handleFiles(event.target.files)}\n />\n\n {value.length > 0 && (\n <ul className=\"space-y-1.5\">\n {value.map((file) => (\n <li\n key={file.id}\n className=\"flex items-center justify-between gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n >\n <a\n href={file.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex min-w-0 items-center gap-2 text-text-primary hover:underline\"\n >\n <FileText className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <span className=\"truncate\">{file.name}</span>\n </a>\n {onRemove && (\n <button\n type=\"button\"\n onClick={() => onRemove(file.id)}\n aria-label={`Remove ${file.name}`}\n className=\"shrink-0 text-text-secondary hover:text-status-error-text\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;AAwBA,IAAa,KAAuC,EAClD,eACA,WAAQ,EAAE,EACV,aACA,WACA,cAAW,IACX,WAAQ,gBACR,mBACI;CACJ,IAAM,EAAE,kBAAe,GAAe,EAChC,IAAW,EAAyB,KAAK,EACzC,CAAC,GAAM,KAAW,EAAS,GAAM,EAEjC,IAAc,OAAO,MAA8B;AACvD,MAAI,CAAC,KAAY,EAAS,WAAW,EAAG;AACxC,IAAQ,GAAK;EACb,IAAM,IAA6B,EAAE;AACrC,MAAI;AACF,QAAK,IAAM,KAAQ,MAAM,KAAK,EAAS,EAAE;IACvC,IAAM,IAAS,MAAM,EAAW,EAAK;AACrC,MAAS,KAAK,EAAO;;AAEvB,GAAI,EAAS,SAAS,MACpB,EAAW,EAAS,EACpB,EAAO,QAAQ,GAAG,EAAS,OAAO,OAAO,EAAS,SAAS,IAAI,MAAM,GAAG,WAAW;WAE9E,GAAK;AACZ,KAAO,MAAM,iBAAkB,EAAc,QAAQ;YAC7C;AAER,GADA,EAAQ,GAAM,EACV,EAAS,YAAS,EAAS,QAAQ,QAAQ;;;AAInD,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C;GACE,kBAAC,UAAD;IACE,MAAK;IACL,UAAU;IACV,eAAe,EAAS,SAAS,OAAO;IACxC,WAAU;cAJZ,CAMG,IAAO,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,GAAG,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EACpF,IAAO,eAAe,EAChB;;GACT,kBAAC,SAAD;IACE,KAAK;IACL,MAAK;IACG;IACE;IACV,WAAU;IACV,WAAW,MAAU,EAAY,EAAM,OAAO,MAAM;IACpD,CAAA;GAED,EAAM,SAAS,KACd,kBAAC,MAAD;IAAI,WAAU;cACX,EAAM,KAAK,MACV,kBAAC,MAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,KAAD;MACE,MAAM,EAAK;MACX,QAAO;MACP,KAAI;MACJ,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAU,WAAU,wCAAyC,CAAA,EAC7D,kBAAC,QAAD;OAAM,WAAU;iBAAY,EAAK;OAAY,CAAA,CAC3C;SACH,KACC,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAS,EAAK,GAAG;MAChC,cAAY,UAAU,EAAK;MAC3B,WAAU;gBAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CAER;OAtBE,EAAK,GAsBP,CACL;IACC,CAAA;GAEH"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { cn as e, formatDate as t } from "../utils/helpers.js";
|
|
2
|
+
import { useCreateEntityComment as n, useEntityComments as r } from "../hooks/platform.js";
|
|
3
|
+
import "../hooks/index.js";
|
|
4
|
+
import { notify as i } from "../lib/toast.js";
|
|
5
|
+
import { useState as a } from "react";
|
|
6
|
+
import { Star as o } from "lucide-react";
|
|
7
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
8
|
+
import { Button as l } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/housingvista/components/ReviewsSection.tsx
|
|
10
|
+
var u = (e) => {
|
|
11
|
+
let t = typeof e == "number" ? e : Number(e);
|
|
12
|
+
return Number.isFinite(t) ? Math.min(5, Math.max(0, Math.round(t))) : 0;
|
|
13
|
+
}, d = ({ value: t, onSelect: n }) => /* @__PURE__ */ s("div", {
|
|
14
|
+
className: "flex items-center gap-0.5",
|
|
15
|
+
children: [
|
|
16
|
+
1,
|
|
17
|
+
2,
|
|
18
|
+
3,
|
|
19
|
+
4,
|
|
20
|
+
5
|
|
21
|
+
].map((r) => {
|
|
22
|
+
let i = /* @__PURE__ */ s(o, { className: e("h-4 w-4", r <= t ? "fill-status-warning text-status-warning" : "text-text-secondary") });
|
|
23
|
+
return n ? /* @__PURE__ */ s("button", {
|
|
24
|
+
type: "button",
|
|
25
|
+
onClick: () => n(r),
|
|
26
|
+
"aria-label": `${r} star`,
|
|
27
|
+
children: i
|
|
28
|
+
}, r) : /* @__PURE__ */ s("span", { children: i }, r);
|
|
29
|
+
})
|
|
30
|
+
}), f = ({ entityId: e, entityType: o, aggregateRating: f, reviewCount: p }) => {
|
|
31
|
+
let { data: m, total: h, loading: g } = r(o, e), { createComment: _, loading: v } = n(), [y, b] = a(0), [x, S] = a("");
|
|
32
|
+
return /* @__PURE__ */ c("section", {
|
|
33
|
+
className: "space-y-4",
|
|
34
|
+
children: [
|
|
35
|
+
/* @__PURE__ */ c("div", {
|
|
36
|
+
className: "flex items-center justify-between",
|
|
37
|
+
children: [/* @__PURE__ */ s("h2", {
|
|
38
|
+
className: "text-lg font-semibold text-text-primary",
|
|
39
|
+
children: "Reviews"
|
|
40
|
+
}), (f ?? 0) > 0 && /* @__PURE__ */ c("div", {
|
|
41
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
42
|
+
children: [/* @__PURE__ */ s(d, { value: u(f) }), /* @__PURE__ */ c("span", { children: [Number(f).toFixed(1), p ? ` · ${p} reviews` : ""] })]
|
|
43
|
+
})]
|
|
44
|
+
}),
|
|
45
|
+
/* @__PURE__ */ c("div", {
|
|
46
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ s("p", {
|
|
49
|
+
className: "mb-2 text-sm font-medium text-text-primary",
|
|
50
|
+
children: "Write a review"
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ s(d, {
|
|
53
|
+
value: y,
|
|
54
|
+
onSelect: b
|
|
55
|
+
}),
|
|
56
|
+
/* @__PURE__ */ s("textarea", {
|
|
57
|
+
value: x,
|
|
58
|
+
onChange: (e) => S(e.target.value),
|
|
59
|
+
rows: 3,
|
|
60
|
+
placeholder: "Share your experience with this provider…",
|
|
61
|
+
className: "mt-3 w-full resize-none rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ s("div", {
|
|
64
|
+
className: "mt-2 flex justify-end",
|
|
65
|
+
children: /* @__PURE__ */ s(l, {
|
|
66
|
+
type: "button",
|
|
67
|
+
onClick: async () => {
|
|
68
|
+
if (y === 0) {
|
|
69
|
+
i.warning("Pick a star rating");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!x.trim()) {
|
|
73
|
+
i.warning("Write a short review");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
await _({
|
|
78
|
+
entityType: o,
|
|
79
|
+
entityId: e,
|
|
80
|
+
text: x.trim(),
|
|
81
|
+
metadata: { stars: y }
|
|
82
|
+
}), i.success("Review posted", "Thanks for sharing your feedback."), b(0), S("");
|
|
83
|
+
} catch (e) {
|
|
84
|
+
i.error("Could not post review", e.message);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
disabled: v,
|
|
88
|
+
children: v ? "Posting…" : "Post review"
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}),
|
|
93
|
+
g ? /* @__PURE__ */ s("p", {
|
|
94
|
+
className: "text-sm text-text-secondary",
|
|
95
|
+
children: "Loading reviews…"
|
|
96
|
+
}) : m.length === 0 ? /* @__PURE__ */ s("p", {
|
|
97
|
+
className: "text-sm text-text-secondary",
|
|
98
|
+
children: "No reviews yet. Be the first to review."
|
|
99
|
+
}) : /* @__PURE__ */ s("ul", {
|
|
100
|
+
className: "space-y-3",
|
|
101
|
+
children: m.map((e) => /* @__PURE__ */ c("li", {
|
|
102
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
103
|
+
children: [/* @__PURE__ */ c("div", {
|
|
104
|
+
className: "mb-1 flex items-center justify-between",
|
|
105
|
+
children: [/* @__PURE__ */ s(d, { value: u(e.metadata?.stars) }), /* @__PURE__ */ s("span", {
|
|
106
|
+
className: "text-xs text-text-secondary",
|
|
107
|
+
children: t(e.createdAt)
|
|
108
|
+
})]
|
|
109
|
+
}), /* @__PURE__ */ s("p", {
|
|
110
|
+
className: "text-sm text-text-primary",
|
|
111
|
+
children: e.text
|
|
112
|
+
})]
|
|
113
|
+
}, e.id))
|
|
114
|
+
}),
|
|
115
|
+
h > m.length && /* @__PURE__ */ c("p", {
|
|
116
|
+
className: "text-xs text-text-secondary",
|
|
117
|
+
children: [
|
|
118
|
+
"Showing ",
|
|
119
|
+
m.length,
|
|
120
|
+
" of ",
|
|
121
|
+
h,
|
|
122
|
+
" reviews."
|
|
123
|
+
]
|
|
124
|
+
})
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
//#endregion
|
|
129
|
+
export { f as ReviewsSection };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=ReviewsSection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReviewsSection.js","names":[],"sources":["../../../src/housingvista/components/ReviewsSection.tsx"],"sourcesContent":["/**\n * Reviews / Q&A surface backed by wspace-conversations-svc EntityComments.\n * Displays persisted reviews (a comment whose `metadata.stars` is set) and lets\n * the user submit a real review — no fabricated ratings. The denormalized\n * aggregate (rating/reviewCount) stays for at-a-glance display.\n * @module housingvista\n */\n\nimport { useState, type FC } from 'react';\nimport { Star } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useEntityComments, useCreateEntityComment } from '../hooks';\nimport { notify } from '../lib/toast';\nimport { cn, formatDate } from '../utils/helpers';\n\ninterface ReviewsSectionProps {\n entityId: string;\n entityType: string;\n /** Denormalized aggregate for the header, if the entity carries one. */\n aggregateRating?: number | null;\n reviewCount?: number | null;\n}\n\nconst toStars = (value: unknown): number => {\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) ? Math.min(5, Math.max(0, Math.round(n))) : 0;\n};\n\nconst StarRow: FC<{ value: number; onSelect?: (v: number) => void }> = ({ value, onSelect }) => (\n <div className=\"flex items-center gap-0.5\">\n {[1, 2, 3, 4, 5].map((star) => {\n const filled = star <= value;\n const icon = (\n <Star className={cn('h-4 w-4', filled ? 'fill-status-warning text-status-warning' : 'text-text-secondary')} />\n );\n return onSelect ? (\n <button key={star} type=\"button\" onClick={() => onSelect(star)} aria-label={`${star} star`}>\n {icon}\n </button>\n ) : (\n <span key={star}>{icon}</span>\n );\n })}\n </div>\n);\n\nexport const ReviewsSection: FC<ReviewsSectionProps> = ({ entityId, entityType, aggregateRating, reviewCount }) => {\n const { data: comments, total, loading } = useEntityComments(entityType, entityId);\n const { createComment, loading: submitting } = useCreateEntityComment();\n const [stars, setStars] = useState(0);\n const [text, setText] = useState('');\n\n const handleSubmit = async () => {\n if (stars === 0) {\n notify.warning('Pick a star rating');\n return;\n }\n if (!text.trim()) {\n notify.warning('Write a short review');\n return;\n }\n try {\n await createComment({ entityType, entityId, text: text.trim(), metadata: { stars } });\n notify.success('Review posted', 'Thanks for sharing your feedback.');\n setStars(0);\n setText('');\n } catch (err) {\n notify.error('Could not post review', (err as Error).message);\n }\n };\n\n return (\n <section className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Reviews</h2>\n {(aggregateRating ?? 0) > 0 && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <StarRow value={toStars(aggregateRating)} />\n <span>\n {Number(aggregateRating).toFixed(1)}\n {reviewCount ? ` · ${reviewCount} reviews` : ''}\n </span>\n </div>\n )}\n </div>\n\n {/* Write a review */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Write a review</p>\n <StarRow value={stars} onSelect={setStars} />\n <textarea\n value={text}\n onChange={(event) => setText(event.target.value)}\n rows={3}\n placeholder=\"Share your experience with this provider…\"\n className=\"mt-3 w-full resize-none rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <div className=\"mt-2 flex justify-end\">\n <Button type=\"button\" onClick={handleSubmit} disabled={submitting}>\n {submitting ? 'Posting…' : 'Post review'}\n </Button>\n </div>\n </div>\n\n {/* Review list */}\n {loading ? (\n <p className=\"text-sm text-text-secondary\">Loading reviews…</p>\n ) : comments.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">No reviews yet. Be the first to review.</p>\n ) : (\n <ul className=\"space-y-3\">\n {comments.map((comment) => (\n <li key={comment.id} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-1 flex items-center justify-between\">\n <StarRow value={toStars(comment.metadata?.stars)} />\n <span className=\"text-xs text-text-secondary\">{formatDate(comment.createdAt)}</span>\n </div>\n <p className=\"text-sm text-text-primary\">{comment.text}</p>\n </li>\n ))}\n </ul>\n )}\n {total > comments.length && (\n <p className=\"text-xs text-text-secondary\">Showing {comments.length} of {total} reviews.</p>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|