@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPage.js","names":[],"sources":["../../../../src/housingvista/pages/chat/ChatPage.tsx"],"sourcesContent":["/**\n * Messages — the real HousingVista inbox. A two-pane conversations UI (thread\n * list + chat thread with bubbles) backed by wspace-conversations-svc:\n * - `useHousingVistaConversations` lists the user's product threads,\n * - `useConversationMessages` streams the selected thread,\n * - `useSendMessage` posts new messages.\n * Everything is persisted server-side; when there are no threads yet we show a\n * tasteful empty state (never fabricated rows). Fully responsive: on mobile the\n * list and the open thread swap places (back button), on desktop they sit\n * side-by-side.\n * @module housingvista\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { ArrowLeft, MapPin, MessageSquare, Search, Send } from 'lucide-react';\nimport { Button, EmphasisPanel, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, EmptyState, StatCard } from '../../components';\nimport { useHousingVistaConversations, type HvConversationSummary } from '../../hooks/messages';\nimport { useConversationMessages, useSendMessage } from '../../hooks';\nimport { useHousingVista } from '../../context/HousingVistaContext';\nimport { notify } from '../../lib/toast';\nimport { cn, getRelativeTime } from '../../utils/helpers';\n\n/** Deterministic accent for an avatar chip, derived from the thread title. */\nconst AVATAR_ACCENTS = [\n 'bg-status-info-bg-subtle text-status-info-text',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-action-primary-bg/15 text-primary',\n];\n\nfunction initials(title: string): string {\n const parts = title.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n}\n\nfunction accentFor(id: string): string {\n let sum = 0;\n for (let i = 0; i < id.length; i += 1) sum += id.charCodeAt(i);\n return AVATAR_ACCENTS[sum % AVATAR_ACCENTS.length];\n}\n\nfunction messageTime(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return '';\n return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });\n}\n\nfunction Avatar({ title, id, size = 'md' }: { title: string; id: string; size?: 'sm' | 'md' }) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full font-semibold',\n size === 'md' ? 'h-10 w-10 text-sm' : 'h-9 w-9 text-xs',\n accentFor(id)\n )}\n aria-hidden\n >\n {initials(title)}\n </span>\n );\n}\n\nexport function ChatPage() {\n const navigate = useNavigate();\n const { currentUser } = useHousingVista();\n const currentUserId = currentUser?.id ?? '';\n\n const { data: conversations, loading, refetch } = useHousingVistaConversations();\n const { sendMessage, loading: sending } = useSendMessage();\n\n const [selectedId, setSelectedId] = useState<string | undefined>();\n const [query, setQuery] = useState('');\n const [draft, setDraft] = useState('');\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Default-select the first thread on desktop once data lands.\n useEffect(() => {\n if (!selectedId && conversations.length > 0 && typeof window !== 'undefined' && window.innerWidth >= 1024) {\n setSelectedId(conversations[0].id);\n }\n }, [conversations, selectedId]);\n\n const selected: HvConversationSummary | undefined = useMemo(\n () => conversations.find((c) => c.id === selectedId),\n [conversations, selectedId]\n );\n\n const { data: messages, loading: loadingMessages } = useConversationMessages(selectedId);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });\n }, [messages, selectedId]);\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return conversations;\n return conversations.filter(\n (c) => c.title.toLowerCase().includes(q) || c.lastMessageText.toLowerCase().includes(q)\n );\n }, [conversations, query]);\n\n const unreadThreads = conversations.filter((c) => c.unreadCount > 0).length;\n\n const handleSend = async () => {\n const text = draft.trim();\n if (!text || !selectedId) return;\n try {\n await sendMessage(selectedId, text);\n setDraft('');\n void refetch();\n } catch (err) {\n notify.error('Message not sent', (err as Error).message);\n }\n };\n\n const emptyInbox = !loading && conversations.length === 0;\n\n return (\n <PageLayout\n title=\"Messages\"\n description=\"Your conversations with property owners, agents, and service providers\"\n purpose=\"Every thread you start from a property, listing, or service lands here — read the full history, pick up where you left off, and reply. Messages are delivered to the other party's inbox in real time.\"\n >\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3\">\n <StatCard label=\"Conversations\" value={conversations.length} icon={<MessageSquare className=\"h-4 w-4\" />} />\n <StatCard label=\"Unread threads\" value={unreadThreads} icon={<MessageSquare className=\"h-4 w-4\" />} />\n <StatCard\n label=\"With updates today\"\n value={conversations.filter((c) => c.lastMessageAt && getRelativeTime(c.lastMessageAt) === 'Today').length}\n icon={<Send className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n {emptyInbox ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<MessageSquare className=\"h-12 w-12\" />}\n title=\"No messages yet\"\n description=\"Start a conversation from any property or service — reach out to an owner or provider and your thread will show up here.\"\n action={<Button onClick={() => navigate('../properties')}>Browse properties</Button>}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <div className=\"grid h-[68vh] min-h-[26rem] grid-cols-1 lg:grid-cols-[20rem_1fr]\">\n {/* Thread list */}\n <aside\n className={cn('flex flex-col border-border-subtle lg:border-r', selectedId ? 'hidden lg:flex' : 'flex')}\n >\n <div className=\"border-b border-border-subtle p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search conversations\"\n aria-label=\"Search conversations\"\n className=\"h-10 w-full rounded-md border border-border-subtle bg-bg-surface pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {loading && conversations.length === 0 ? (\n <p className=\"p-6 text-center text-sm text-text-secondary\">Loading conversations…</p>\n ) : filtered.length === 0 ? (\n <p className=\"p-6 text-center text-sm text-text-secondary\">No conversations match your search.</p>\n ) : (\n <ul>\n {filtered.map((conversation) => {\n const active = conversation.id === selectedId;\n return (\n <li key={conversation.id}>\n <button\n type=\"button\"\n onClick={() => {\n setSelectedId(conversation.id);\n setDraft('');\n }}\n className={cn(\n 'flex w-full items-start gap-3 border-b border-border-subtle px-3 py-3 text-left transition-colors',\n active ? 'bg-action-primary-bg/10' : 'hover:bg-bg-sunken'\n )}\n >\n <Avatar title={conversation.title} id={conversation.id} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">\n {conversation.title}\n </span>\n {conversation.lastMessageAt && (\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {getRelativeTime(conversation.lastMessageAt)}\n </span>\n )}\n </div>\n <div className=\"mt-0.5 flex items-center gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-xs text-text-secondary\">\n {conversation.lastMessageText || 'No messages yet'}\n </span>\n {conversation.unreadCount > 0 && (\n <span className=\"inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-action-primary-bg px-1.5 text-[0.65rem] font-semibold text-action-primary-text\">\n {conversation.unreadCount}\n </span>\n )}\n </div>\n </div>\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </aside>\n\n {/* Thread */}\n <section className={cn('flex min-h-0 flex-col', selectedId ? 'flex' : 'hidden lg:flex')}>\n {selected ? (\n <>\n <header className=\"flex items-center gap-3 border-b border-border-subtle p-3\">\n <button\n type=\"button\"\n onClick={() => setSelectedId(undefined)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken lg:hidden\"\n aria-label=\"Back to conversations\"\n >\n <ArrowLeft className=\"h-5 w-5\" />\n </button>\n <Avatar title={selected.title} id={selected.id} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">{selected.title}</h2>\n {selected.contextHref && (\n <button\n type=\"button\"\n onClick={() => navigate(`..${selected.contextHref}`)}\n className=\"flex items-center gap-1 text-xs text-text-secondary transition-colors hover:text-primary\"\n >\n <MapPin className=\"h-3 w-3\" />\n View listing\n </button>\n )}\n </div>\n </header>\n\n <div ref={scrollRef} className=\"min-h-0 flex-1 space-y-2 overflow-y-auto bg-bg-sunken/40 p-4\">\n {loadingMessages && messages.length === 0 ? (\n <p className=\"py-10 text-center text-sm text-text-secondary\">Loading messages…</p>\n ) : messages.length === 0 ? (\n <p className=\"py-10 text-center text-sm text-text-secondary\">\n No messages yet. Say hello to get the conversation started.\n </p>\n ) : (\n messages.map((message) => {\n const mine = message.authorId === currentUserId;\n return (\n <div key={message.id} className={cn('flex', mine ? 'justify-end' : 'justify-start')}>\n <div className={cn('max-w-[78%]', mine ? 'items-end' : 'items-start')}>\n <div\n className={cn(\n 'rounded-2xl px-3.5 py-2 text-sm',\n mine\n ? 'rounded-br-sm bg-action-primary-bg text-action-primary-text'\n : 'rounded-bl-sm border border-border-subtle bg-bg-surface text-text-primary'\n )}\n >\n {message.text}\n </div>\n <div className={cn('mt-1 px-1 text-[0.65rem] text-text-secondary', mine && 'text-right')}>\n {messageTime(message.createdAt)}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n <div className=\"flex items-end gap-2 border-t border-border-subtle p-3\">\n <textarea\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n void handleSend();\n }\n }}\n rows={1}\n placeholder=\"Type a message…\"\n aria-label=\"Message\"\n className=\"max-h-32 min-h-10 flex-1 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 <Button type=\"button\" onClick={handleSend} disabled={!draft.trim() || sending}>\n <Send className=\"h-4 w-4\" />\n <span className=\"ml-1 hidden sm:inline\">Send</span>\n </Button>\n </div>\n </>\n ) : (\n <div className=\"hidden flex-1 items-center justify-center p-8 lg:flex\">\n <div className=\"text-center\">\n <MessageSquare className=\"mx-auto h-10 w-10 text-text-secondary\" />\n <p className=\"mt-3 text-sm text-text-secondary\">Select a conversation to read and reply.</p>\n </div>\n </div>\n )}\n </section>\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatPage.js","names":[],"sources":["../../../../src/housingvista/pages/chat/ChatPage.tsx"],"sourcesContent":["/**\n * Messages — the real HousingVista inbox. A two-pane conversations UI (thread\n * list + chat thread with bubbles) backed by wspace-conversations-svc:\n * - `useHousingVistaConversations` lists the user's product threads,\n * - `useConversationMessages` streams the selected thread,\n * - `useSendMessage` posts new messages.\n * Everything is persisted server-side; when there are no threads yet we show a\n * tasteful empty state (never fabricated rows). Fully responsive: on mobile the\n * list and the open thread swap places (back button), on desktop they sit\n * side-by-side.\n * @module housingvista\n */\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { ArrowLeft, MapPin, MessageSquare, Search, Send } from 'lucide-react';\nimport { Button, EmphasisPanel, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, EmptyState, StatCard } from '../../components';\nimport { useHousingVistaConversations, type HvConversationSummary } from '../../hooks/messages';\nimport { useConversationMessages, useSendMessage } from '../../hooks';\nimport { useHousingVista } from '../../context/HousingVistaContext';\nimport { notify } from '../../lib/toast';\nimport { cn, getRelativeTime } from '../../utils/helpers';\nimport { useTrackPageView } from '../../hooks/useHousingVistaAnalytics';\n\n/** Deterministic accent for an avatar chip, derived from the thread title. */\nconst AVATAR_ACCENTS = [\n 'bg-status-info-bg-subtle text-status-info-text',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-action-primary-bg/15 text-primary',\n];\n\nfunction initials(title: string): string {\n const parts = title.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n}\n\nfunction accentFor(id: string): string {\n let sum = 0;\n for (let i = 0; i < id.length; i += 1) sum += id.charCodeAt(i);\n return AVATAR_ACCENTS[sum % AVATAR_ACCENTS.length];\n}\n\nfunction messageTime(iso: string): string {\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return '';\n return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });\n}\n\nfunction Avatar({ title, id, size = 'md' }: { title: string; id: string; size?: 'sm' | 'md' }) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full font-semibold',\n size === 'md' ? 'h-10 w-10 text-sm' : 'h-9 w-9 text-xs',\n accentFor(id)\n )}\n aria-hidden\n >\n {initials(title)}\n </span>\n );\n}\n\nexport function ChatPage() {\n useTrackPageView('housingvista.chat.viewed');\n const navigate = useNavigate();\n const { currentUser } = useHousingVista();\n const currentUserId = currentUser?.id ?? '';\n\n const { data: conversations, loading, refetch } = useHousingVistaConversations();\n const { sendMessage, loading: sending } = useSendMessage();\n\n const [selectedId, setSelectedId] = useState<string | undefined>();\n const [query, setQuery] = useState('');\n const [draft, setDraft] = useState('');\n const scrollRef = useRef<HTMLDivElement>(null);\n\n // Default-select the first thread on desktop once data lands.\n useEffect(() => {\n if (!selectedId && conversations.length > 0 && typeof window !== 'undefined' && window.innerWidth >= 1024) {\n setSelectedId(conversations[0].id);\n }\n }, [conversations, selectedId]);\n\n const selected: HvConversationSummary | undefined = useMemo(\n () => conversations.find((c) => c.id === selectedId),\n [conversations, selectedId]\n );\n\n const { data: messages, loading: loadingMessages } = useConversationMessages(selectedId);\n\n useEffect(() => {\n scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight });\n }, [messages, selectedId]);\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return conversations;\n return conversations.filter(\n (c) => c.title.toLowerCase().includes(q) || c.lastMessageText.toLowerCase().includes(q)\n );\n }, [conversations, query]);\n\n const unreadThreads = conversations.filter((c) => c.unreadCount > 0).length;\n\n const handleSend = async () => {\n const text = draft.trim();\n if (!text || !selectedId) return;\n try {\n await sendMessage(selectedId, text);\n setDraft('');\n void refetch();\n } catch (err) {\n notify.error('Message not sent', (err as Error).message);\n }\n };\n\n const emptyInbox = !loading && conversations.length === 0;\n\n return (\n <PageLayout\n title=\"Messages\"\n description=\"Your conversations with property owners, agents, and service providers\"\n purpose=\"Every thread you start from a property, listing, or service lands here — read the full history, pick up where you left off, and reply. Messages are delivered to the other party's inbox in real time.\"\n >\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3\">\n <StatCard label=\"Conversations\" value={conversations.length} icon={<MessageSquare className=\"h-4 w-4\" />} />\n <StatCard label=\"Unread threads\" value={unreadThreads} icon={<MessageSquare className=\"h-4 w-4\" />} />\n <StatCard\n label=\"With updates today\"\n value={conversations.filter((c) => c.lastMessageAt && getRelativeTime(c.lastMessageAt) === 'Today').length}\n icon={<Send className=\"h-4 w-4\" />}\n />\n </div>\n </EmphasisPanel>\n\n {emptyInbox ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<MessageSquare className=\"h-12 w-12\" />}\n title=\"No messages yet\"\n description=\"Start a conversation from any property or service — reach out to an owner or provider and your thread will show up here.\"\n action={<Button onClick={() => navigate('../properties')}>Browse properties</Button>}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <div className=\"grid h-[68vh] min-h-[26rem] grid-cols-1 lg:grid-cols-[20rem_1fr]\">\n {/* Thread list */}\n <aside\n className={cn('flex flex-col border-border-subtle lg:border-r', selectedId ? 'hidden lg:flex' : 'flex')}\n >\n <div className=\"border-b border-border-subtle p-3\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Search conversations\"\n aria-label=\"Search conversations\"\n className=\"h-10 w-full rounded-md border border-border-subtle bg-bg-surface pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {loading && conversations.length === 0 ? (\n <p className=\"p-6 text-center text-sm text-text-secondary\">Loading conversations…</p>\n ) : filtered.length === 0 ? (\n <p className=\"p-6 text-center text-sm text-text-secondary\">No conversations match your search.</p>\n ) : (\n <ul>\n {filtered.map((conversation) => {\n const active = conversation.id === selectedId;\n return (\n <li key={conversation.id}>\n <button\n type=\"button\"\n onClick={() => {\n setSelectedId(conversation.id);\n setDraft('');\n }}\n className={cn(\n 'flex w-full items-start gap-3 border-b border-border-subtle px-3 py-3 text-left transition-colors',\n active ? 'bg-action-primary-bg/10' : 'hover:bg-bg-sunken'\n )}\n >\n <Avatar title={conversation.title} id={conversation.id} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-sm font-semibold text-text-primary\">\n {conversation.title}\n </span>\n {conversation.lastMessageAt && (\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {getRelativeTime(conversation.lastMessageAt)}\n </span>\n )}\n </div>\n <div className=\"mt-0.5 flex items-center gap-2\">\n <span className=\"min-w-0 flex-1 truncate text-xs text-text-secondary\">\n {conversation.lastMessageText || 'No messages yet'}\n </span>\n {conversation.unreadCount > 0 && (\n <span className=\"inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-full bg-action-primary-bg px-1.5 text-[0.65rem] font-semibold text-action-primary-text\">\n {conversation.unreadCount}\n </span>\n )}\n </div>\n </div>\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </aside>\n\n {/* Thread */}\n <section className={cn('flex min-h-0 flex-col', selectedId ? 'flex' : 'hidden lg:flex')}>\n {selected ? (\n <>\n <header className=\"flex items-center gap-3 border-b border-border-subtle p-3\">\n <button\n type=\"button\"\n onClick={() => setSelectedId(undefined)}\n className=\"rounded-md p-1.5 text-text-secondary transition-colors hover:bg-bg-sunken lg:hidden\"\n aria-label=\"Back to conversations\"\n >\n <ArrowLeft className=\"h-5 w-5\" />\n </button>\n <Avatar title={selected.title} id={selected.id} size=\"sm\" />\n <div className=\"min-w-0 flex-1\">\n <h2 className=\"truncate text-sm font-semibold text-text-primary\">{selected.title}</h2>\n {selected.contextHref && (\n <button\n type=\"button\"\n onClick={() => navigate(`..${selected.contextHref}`)}\n className=\"flex items-center gap-1 text-xs text-text-secondary transition-colors hover:text-primary\"\n >\n <MapPin className=\"h-3 w-3\" />\n View listing\n </button>\n )}\n </div>\n </header>\n\n <div ref={scrollRef} className=\"min-h-0 flex-1 space-y-2 overflow-y-auto bg-bg-sunken/40 p-4\">\n {loadingMessages && messages.length === 0 ? (\n <p className=\"py-10 text-center text-sm text-text-secondary\">Loading messages…</p>\n ) : messages.length === 0 ? (\n <p className=\"py-10 text-center text-sm text-text-secondary\">\n No messages yet. Say hello to get the conversation started.\n </p>\n ) : (\n messages.map((message) => {\n const mine = message.authorId === currentUserId;\n return (\n <div key={message.id} className={cn('flex', mine ? 'justify-end' : 'justify-start')}>\n <div className={cn('max-w-[78%]', mine ? 'items-end' : 'items-start')}>\n <div\n className={cn(\n 'rounded-2xl px-3.5 py-2 text-sm',\n mine\n ? 'rounded-br-sm bg-action-primary-bg text-action-primary-text'\n : 'rounded-bl-sm border border-border-subtle bg-bg-surface text-text-primary'\n )}\n >\n {message.text}\n </div>\n <div className={cn('mt-1 px-1 text-[0.65rem] text-text-secondary', mine && 'text-right')}>\n {messageTime(message.createdAt)}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n <div className=\"flex items-end gap-2 border-t border-border-subtle p-3\">\n <textarea\n value={draft}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n void handleSend();\n }\n }}\n rows={1}\n placeholder=\"Type a message…\"\n aria-label=\"Message\"\n className=\"max-h-32 min-h-10 flex-1 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 <Button type=\"button\" onClick={handleSend} disabled={!draft.trim() || sending}>\n <Send className=\"h-4 w-4\" />\n <span className=\"ml-1 hidden sm:inline\">Send</span>\n </Button>\n </div>\n </>\n ) : (\n <div className=\"hidden flex-1 items-center justify-center p-8 lg:flex\">\n <div className=\"text-center\">\n <MessageSquare className=\"mx-auto h-10 w-10 text-text-secondary\" />\n <p className=\"mt-3 text-sm text-text-secondary\">Select a conversation to read and reply.</p>\n </div>\n </div>\n )}\n </section>\n </div>\n </GlassCard>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HomeLoanDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/finance/HomeLoanDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"HomeLoanDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/pages/finance/HomeLoanDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH,wBAAgB,kBAAkB,gCAuQjC"}
|
|
@@ -3,210 +3,213 @@ import { StatusBadge as r } from "../../components/StatusBadge.js";
|
|
|
3
3
|
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
4
4
|
import { Breadcrumbs as a } from "../../components/Breadcrumbs.js";
|
|
5
5
|
import { LoadingSkeleton as o } from "../../components/LoadingSkeleton.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { useTrackPageView as s } from "../../hooks/useHousingVistaAnalytics.js";
|
|
7
|
+
import { useHomeLoanOffer as c } from "../../hooks/index.js";
|
|
8
|
+
import { ContactOwnerDialog as l } from "../../components/ContactOwnerDialog.js";
|
|
8
9
|
import "../../components/index.js";
|
|
9
|
-
import { useMemo as
|
|
10
|
-
import { ArrowLeft as
|
|
11
|
-
import { useLocation as
|
|
12
|
-
import { jsx as
|
|
13
|
-
import { Button as
|
|
10
|
+
import { useMemo as u, useState as d } from "react";
|
|
11
|
+
import { ArrowLeft as f, Building2 as p, Calculator as m, CalendarClock as h, CheckCircle as g, Gift as _, Percent as v, Star as y, Wallet as b } from "lucide-react";
|
|
12
|
+
import { useLocation as x, useNavigate as S, useParams as C } from "react-router-dom";
|
|
13
|
+
import { jsx as w, jsxs as T } from "react/jsx-runtime";
|
|
14
|
+
import { Button as E, EmphasisPanel as D, GlassCard as O, PagePurpose as k } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/housingvista/pages/finance/HomeLoanDetailPage.tsx
|
|
15
|
-
var
|
|
16
|
-
function
|
|
16
|
+
var A = "w-full accent-[var(--color-action-primary-bg)]";
|
|
17
|
+
function j(e, t, n) {
|
|
17
18
|
if (e <= 0 || t <= 0 || n <= 0) return 0;
|
|
18
19
|
let r = t / 12 / 100, i = n * 12, a = e * r * (1 + r) ** +i / ((1 + r) ** +i - 1);
|
|
19
20
|
return Math.round(a);
|
|
20
21
|
}
|
|
21
|
-
function
|
|
22
|
-
return /* @__PURE__ */
|
|
22
|
+
function M({ rating: t }) {
|
|
23
|
+
return /* @__PURE__ */ w("div", {
|
|
23
24
|
className: "flex items-center gap-0.5 text-status-warning-text",
|
|
24
|
-
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */
|
|
25
|
+
children: Array.from({ length: 5 }).map((n, r) => /* @__PURE__ */ w(y, { className: e("h-4 w-4", r < Math.floor(t) ? "fill-current" : "fill-none text-text-secondary/30") }, r))
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
|
-
function
|
|
28
|
-
let { id:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
function N() {
|
|
29
|
+
let { id: y } = C();
|
|
30
|
+
s("housingvista.home_loan.viewed", y);
|
|
31
|
+
let N = S(), P = x(), { data: F, loading: I } = c(y), [L, R] = d(5e5), [z, B] = d(20), [V, H] = d(!1), U = F?.interestRate ?? 0, W = u(() => j(L, U, z), [
|
|
32
|
+
L,
|
|
33
|
+
U,
|
|
34
|
+
z
|
|
35
|
+
]), G = W * z * 12, K = G - L;
|
|
36
|
+
if (I) return /* @__PURE__ */ w("div", {
|
|
34
37
|
className: "p-6",
|
|
35
|
-
children: /* @__PURE__ */
|
|
38
|
+
children: /* @__PURE__ */ w(o, { variant: "detail" })
|
|
36
39
|
});
|
|
37
|
-
if (!
|
|
40
|
+
if (!F) return /* @__PURE__ */ w("div", {
|
|
38
41
|
className: "p-6",
|
|
39
|
-
children: /* @__PURE__ */
|
|
42
|
+
children: /* @__PURE__ */ w(i, {
|
|
40
43
|
illustration: "empty-data",
|
|
41
|
-
icon: /* @__PURE__ */
|
|
44
|
+
icon: /* @__PURE__ */ w(p, { className: "h-12 w-12" }),
|
|
42
45
|
title: "Loan Offer Not Found",
|
|
43
46
|
description: "The loan offer you're looking for doesn't exist or is no longer available.",
|
|
44
|
-
action: /* @__PURE__ */
|
|
45
|
-
onClick: () =>
|
|
47
|
+
action: /* @__PURE__ */ T("button", {
|
|
48
|
+
onClick: () => N("../home-loans"),
|
|
46
49
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover",
|
|
47
|
-
children: [/* @__PURE__ */
|
|
50
|
+
children: [/* @__PURE__ */ w(f, { className: "h-4 w-4" }), "Back to Home Loans"]
|
|
48
51
|
})
|
|
49
52
|
})
|
|
50
53
|
});
|
|
51
|
-
let
|
|
52
|
-
return /* @__PURE__ */
|
|
54
|
+
let q = F.features ?? [];
|
|
55
|
+
return /* @__PURE__ */ T("div", {
|
|
53
56
|
className: "flex flex-col gap-6 p-6",
|
|
54
57
|
children: [
|
|
55
|
-
/* @__PURE__ */
|
|
58
|
+
/* @__PURE__ */ w(a, { items: [{
|
|
56
59
|
label: "Home Loans",
|
|
57
60
|
href: "../"
|
|
58
|
-
}, { label:
|
|
59
|
-
/* @__PURE__ */
|
|
61
|
+
}, { label: F.bankName }] }),
|
|
62
|
+
/* @__PURE__ */ T("div", {
|
|
60
63
|
className: "flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",
|
|
61
|
-
children: [/* @__PURE__ */
|
|
64
|
+
children: [/* @__PURE__ */ T("div", {
|
|
62
65
|
className: "flex items-start gap-4",
|
|
63
|
-
children: [/* @__PURE__ */
|
|
66
|
+
children: [/* @__PURE__ */ w("div", {
|
|
64
67
|
className: "flex h-12 w-12 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-text-secondary",
|
|
65
|
-
children: /* @__PURE__ */
|
|
66
|
-
}), /* @__PURE__ */
|
|
67
|
-
/* @__PURE__ */
|
|
68
|
-
onClick: () =>
|
|
68
|
+
children: /* @__PURE__ */ w(p, { className: "h-6 w-6" })
|
|
69
|
+
}), /* @__PURE__ */ T("div", { children: [
|
|
70
|
+
/* @__PURE__ */ T("button", {
|
|
71
|
+
onClick: () => N("../home-loans"),
|
|
69
72
|
className: "mb-2 inline-flex items-center gap-2 rounded-md px-2 py-1 text-sm text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
|
|
70
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ w(f, { className: "h-4 w-4" }), "Back to Home Loans"]
|
|
71
74
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
75
|
+
/* @__PURE__ */ w("h1", {
|
|
73
76
|
className: "text-2xl font-semibold text-text-primary",
|
|
74
|
-
children:
|
|
77
|
+
children: F.bankName
|
|
75
78
|
}),
|
|
76
|
-
/* @__PURE__ */
|
|
79
|
+
/* @__PURE__ */ T("div", {
|
|
77
80
|
className: "mt-2 flex flex-wrap items-center gap-2",
|
|
78
|
-
children: [
|
|
81
|
+
children: [F.rating != null && /* @__PURE__ */ T("span", {
|
|
79
82
|
className: "inline-flex items-center gap-1.5",
|
|
80
|
-
children: [/* @__PURE__ */
|
|
83
|
+
children: [/* @__PURE__ */ w(M, { rating: F.rating }), /* @__PURE__ */ w("span", {
|
|
81
84
|
className: "text-sm text-text-secondary",
|
|
82
|
-
children:
|
|
85
|
+
children: F.rating
|
|
83
86
|
})]
|
|
84
|
-
}),
|
|
87
|
+
}), F.specialOffer && /* @__PURE__ */ w(r, {
|
|
85
88
|
label: "Special Offer",
|
|
86
89
|
variant: "warning"
|
|
87
90
|
})]
|
|
88
91
|
})
|
|
89
92
|
] })]
|
|
90
|
-
}), /* @__PURE__ */
|
|
93
|
+
}), /* @__PURE__ */ w(E, {
|
|
91
94
|
type: "button",
|
|
92
95
|
variant: "default",
|
|
93
|
-
onClick: () =>
|
|
96
|
+
onClick: () => H(!0),
|
|
94
97
|
children: "Apply Now"
|
|
95
98
|
})]
|
|
96
99
|
}),
|
|
97
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ T(k, {
|
|
98
101
|
className: "max-w-3xl",
|
|
99
102
|
children: [
|
|
100
103
|
"The full terms of this offer with a live EMI estimate at the bank's headline rate. Model your loan amount and tenure to see the real monthly cost, then apply to connect with ",
|
|
101
|
-
|
|
104
|
+
F.bankName,
|
|
102
105
|
" through Messages."
|
|
103
106
|
]
|
|
104
107
|
}),
|
|
105
|
-
|
|
108
|
+
F.specialOffer && /* @__PURE__ */ T("div", {
|
|
106
109
|
className: "flex items-center gap-2 rounded-lg bg-status-warning-bg-subtle p-3 text-sm text-status-warning-text",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ w(_, { className: "h-4 w-4 shrink-0" }), F.specialOffer]
|
|
108
111
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
112
|
+
/* @__PURE__ */ T(D, {
|
|
110
113
|
className: "p-5",
|
|
111
|
-
children: [/* @__PURE__ */
|
|
114
|
+
children: [/* @__PURE__ */ T("div", {
|
|
112
115
|
className: "mb-4 flex items-center gap-2",
|
|
113
|
-
children: [/* @__PURE__ */
|
|
116
|
+
children: [/* @__PURE__ */ w(m, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T("h2", {
|
|
114
117
|
className: "text-lg font-semibold text-text-primary",
|
|
115
118
|
children: [
|
|
116
119
|
"EMI Estimate at ",
|
|
117
|
-
|
|
120
|
+
F.interestRate,
|
|
118
121
|
"%"
|
|
119
122
|
]
|
|
120
123
|
})]
|
|
121
|
-
}), /* @__PURE__ */
|
|
124
|
+
}), /* @__PURE__ */ T("div", {
|
|
122
125
|
className: "grid gap-6 md:grid-cols-2",
|
|
123
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ T("div", {
|
|
124
127
|
className: "space-y-5",
|
|
125
|
-
children: [/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
128
|
+
children: [/* @__PURE__ */ T("div", { children: [
|
|
129
|
+
/* @__PURE__ */ T("div", {
|
|
127
130
|
className: "mb-2 flex items-center justify-between",
|
|
128
|
-
children: [/* @__PURE__ */
|
|
131
|
+
children: [/* @__PURE__ */ w("label", {
|
|
129
132
|
className: "text-sm font-medium text-text-primary",
|
|
130
133
|
children: "Loan Amount"
|
|
131
|
-
}), /* @__PURE__ */
|
|
134
|
+
}), /* @__PURE__ */ w("span", {
|
|
132
135
|
className: "text-sm font-semibold text-text-primary",
|
|
133
|
-
children: t(
|
|
136
|
+
children: t(L)
|
|
134
137
|
})]
|
|
135
138
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
139
|
+
/* @__PURE__ */ w("input", {
|
|
137
140
|
type: "range",
|
|
138
141
|
min: 5e4,
|
|
139
|
-
max: Math.max(
|
|
142
|
+
max: Math.max(F.maxAmount, 1e5),
|
|
140
143
|
step: 1e4,
|
|
141
|
-
value: Math.min(
|
|
142
|
-
onChange: (e) =>
|
|
144
|
+
value: Math.min(L, F.maxAmount),
|
|
145
|
+
onChange: (e) => R(Number(e.target.value)),
|
|
143
146
|
"aria-label": "Loan amount",
|
|
144
|
-
className:
|
|
147
|
+
className: A
|
|
145
148
|
}),
|
|
146
|
-
/* @__PURE__ */
|
|
149
|
+
/* @__PURE__ */ T("div", {
|
|
147
150
|
className: "mt-1 flex justify-between text-xs text-text-secondary",
|
|
148
|
-
children: [/* @__PURE__ */
|
|
151
|
+
children: [/* @__PURE__ */ w("span", { children: "₹50K" }), /* @__PURE__ */ T("span", { children: ["Max ", n(F.maxAmount)] })]
|
|
149
152
|
})
|
|
150
|
-
] }), /* @__PURE__ */
|
|
151
|
-
/* @__PURE__ */
|
|
153
|
+
] }), /* @__PURE__ */ T("div", { children: [
|
|
154
|
+
/* @__PURE__ */ T("div", {
|
|
152
155
|
className: "mb-2 flex items-center justify-between",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
156
|
+
children: [/* @__PURE__ */ w("label", {
|
|
154
157
|
className: "text-sm font-medium text-text-primary",
|
|
155
158
|
children: "Tenure (years)"
|
|
156
|
-
}), /* @__PURE__ */
|
|
159
|
+
}), /* @__PURE__ */ T("span", {
|
|
157
160
|
className: "text-sm font-semibold text-text-primary",
|
|
158
|
-
children: [
|
|
161
|
+
children: [z, " years"]
|
|
159
162
|
})]
|
|
160
163
|
}),
|
|
161
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ w("input", {
|
|
162
165
|
type: "range",
|
|
163
166
|
min: 1,
|
|
164
|
-
max:
|
|
167
|
+
max: F.maxTenure,
|
|
165
168
|
step: 1,
|
|
166
|
-
value: Math.min(
|
|
167
|
-
onChange: (e) =>
|
|
169
|
+
value: Math.min(z, F.maxTenure),
|
|
170
|
+
onChange: (e) => B(Number(e.target.value)),
|
|
168
171
|
"aria-label": "Tenure in years",
|
|
169
|
-
className:
|
|
172
|
+
className: A
|
|
170
173
|
}),
|
|
171
|
-
/* @__PURE__ */
|
|
174
|
+
/* @__PURE__ */ T("div", {
|
|
172
175
|
className: "mt-1 flex justify-between text-xs text-text-secondary",
|
|
173
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ w("span", { children: "1 yr" }), /* @__PURE__ */ T("span", { children: [
|
|
174
177
|
"Max ",
|
|
175
|
-
|
|
178
|
+
F.maxTenure,
|
|
176
179
|
" yrs"
|
|
177
180
|
] })]
|
|
178
181
|
})
|
|
179
182
|
] })]
|
|
180
|
-
}), /* @__PURE__ */
|
|
183
|
+
}), /* @__PURE__ */ T("div", {
|
|
181
184
|
className: "flex flex-col items-center justify-center rounded-lg bg-bg-sunken/50 p-6",
|
|
182
185
|
children: [
|
|
183
|
-
/* @__PURE__ */
|
|
186
|
+
/* @__PURE__ */ w("p", {
|
|
184
187
|
className: "text-sm text-text-secondary",
|
|
185
188
|
children: "Monthly EMI"
|
|
186
189
|
}),
|
|
187
|
-
/* @__PURE__ */
|
|
190
|
+
/* @__PURE__ */ w("p", {
|
|
188
191
|
className: "my-2 text-4xl font-bold text-text-primary",
|
|
189
|
-
children: t(
|
|
192
|
+
children: t(W)
|
|
190
193
|
}),
|
|
191
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ T("div", {
|
|
192
195
|
className: "mt-4 grid w-full grid-cols-2 gap-4",
|
|
193
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ T("div", {
|
|
194
197
|
className: "text-center",
|
|
195
|
-
children: [/* @__PURE__ */
|
|
198
|
+
children: [/* @__PURE__ */ w("p", {
|
|
196
199
|
className: "text-xs text-text-secondary",
|
|
197
200
|
children: "Total Payment"
|
|
198
|
-
}), /* @__PURE__ */
|
|
201
|
+
}), /* @__PURE__ */ w("p", {
|
|
199
202
|
className: "text-sm font-semibold text-text-primary",
|
|
200
|
-
children: t(
|
|
203
|
+
children: t(G)
|
|
201
204
|
})]
|
|
202
|
-
}), /* @__PURE__ */
|
|
205
|
+
}), /* @__PURE__ */ T("div", {
|
|
203
206
|
className: "text-center",
|
|
204
|
-
children: [/* @__PURE__ */
|
|
207
|
+
children: [/* @__PURE__ */ w("p", {
|
|
205
208
|
className: "text-xs text-text-secondary",
|
|
206
209
|
children: "Total Interest"
|
|
207
|
-
}), /* @__PURE__ */
|
|
210
|
+
}), /* @__PURE__ */ w("p", {
|
|
208
211
|
className: "text-sm font-semibold text-status-error-text",
|
|
209
|
-
children: t(
|
|
212
|
+
children: t(K)
|
|
210
213
|
})]
|
|
211
214
|
})]
|
|
212
215
|
})
|
|
@@ -214,138 +217,138 @@ function M() {
|
|
|
214
217
|
})]
|
|
215
218
|
})]
|
|
216
219
|
}),
|
|
217
|
-
/* @__PURE__ */
|
|
220
|
+
/* @__PURE__ */ T("div", {
|
|
218
221
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
219
|
-
children: [/* @__PURE__ */
|
|
222
|
+
children: [/* @__PURE__ */ T("div", {
|
|
220
223
|
className: "space-y-6 lg:col-span-2",
|
|
221
|
-
children: [/* @__PURE__ */
|
|
224
|
+
children: [/* @__PURE__ */ T(O, {
|
|
222
225
|
treatment: "plain",
|
|
223
226
|
className: "border-border-seam p-6",
|
|
224
|
-
children: [/* @__PURE__ */
|
|
227
|
+
children: [/* @__PURE__ */ w("h2", {
|
|
225
228
|
className: "mb-4 text-base font-semibold text-text-primary",
|
|
226
229
|
children: "Loan Terms"
|
|
227
|
-
}), /* @__PURE__ */
|
|
230
|
+
}), /* @__PURE__ */ T("div", {
|
|
228
231
|
className: "grid grid-cols-2 gap-4 sm:grid-cols-3",
|
|
229
232
|
children: [
|
|
230
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ T("div", {
|
|
231
234
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
232
|
-
children: [/* @__PURE__ */
|
|
235
|
+
children: [/* @__PURE__ */ T("div", {
|
|
233
236
|
className: "flex items-center gap-1.5 text-xs text-text-secondary",
|
|
234
|
-
children: [/* @__PURE__ */
|
|
235
|
-
}), /* @__PURE__ */
|
|
237
|
+
children: [/* @__PURE__ */ w(v, { className: "h-3.5 w-3.5" }), "Interest Rate"]
|
|
238
|
+
}), /* @__PURE__ */ T("div", {
|
|
236
239
|
className: "mt-1 text-lg font-bold text-text-primary",
|
|
237
|
-
children: [
|
|
240
|
+
children: [F.interestRate, "%"]
|
|
238
241
|
})]
|
|
239
242
|
}),
|
|
240
|
-
/* @__PURE__ */
|
|
243
|
+
/* @__PURE__ */ T("div", {
|
|
241
244
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
242
|
-
children: [/* @__PURE__ */
|
|
245
|
+
children: [/* @__PURE__ */ T("div", {
|
|
243
246
|
className: "flex items-center gap-1.5 text-xs text-text-secondary",
|
|
244
|
-
children: [/* @__PURE__ */
|
|
245
|
-
}), /* @__PURE__ */
|
|
247
|
+
children: [/* @__PURE__ */ w(b, { className: "h-3.5 w-3.5" }), "EMI / Lakh"]
|
|
248
|
+
}), /* @__PURE__ */ w("div", {
|
|
246
249
|
className: "mt-1 text-lg font-bold text-text-primary",
|
|
247
|
-
children:
|
|
250
|
+
children: F.emiPerLakh == null ? "—" : n(F.emiPerLakh)
|
|
248
251
|
})]
|
|
249
252
|
}),
|
|
250
|
-
/* @__PURE__ */
|
|
253
|
+
/* @__PURE__ */ T("div", {
|
|
251
254
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
252
|
-
children: [/* @__PURE__ */
|
|
255
|
+
children: [/* @__PURE__ */ T("div", {
|
|
253
256
|
className: "flex items-center gap-1.5 text-xs text-text-secondary",
|
|
254
|
-
children: [/* @__PURE__ */
|
|
255
|
-
}), /* @__PURE__ */
|
|
257
|
+
children: [/* @__PURE__ */ w(h, { className: "h-3.5 w-3.5" }), "Max Tenure"]
|
|
258
|
+
}), /* @__PURE__ */ T("div", {
|
|
256
259
|
className: "mt-1 text-lg font-bold text-text-primary",
|
|
257
|
-
children: [
|
|
260
|
+
children: [F.maxTenure, " yrs"]
|
|
258
261
|
})]
|
|
259
262
|
}),
|
|
260
|
-
/* @__PURE__ */
|
|
263
|
+
/* @__PURE__ */ T("div", {
|
|
261
264
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
262
|
-
children: [/* @__PURE__ */
|
|
265
|
+
children: [/* @__PURE__ */ w("div", {
|
|
263
266
|
className: "text-xs text-text-secondary",
|
|
264
267
|
children: "Max Amount"
|
|
265
|
-
}), /* @__PURE__ */
|
|
268
|
+
}), /* @__PURE__ */ w("div", {
|
|
266
269
|
className: "mt-1 text-sm font-semibold text-text-primary",
|
|
267
|
-
children: n(
|
|
270
|
+
children: n(F.maxAmount)
|
|
268
271
|
})]
|
|
269
272
|
}),
|
|
270
|
-
/* @__PURE__ */
|
|
273
|
+
/* @__PURE__ */ T("div", {
|
|
271
274
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
272
|
-
children: [/* @__PURE__ */
|
|
275
|
+
children: [/* @__PURE__ */ w("div", {
|
|
273
276
|
className: "text-xs text-text-secondary",
|
|
274
277
|
children: "Processing Fee"
|
|
275
|
-
}), /* @__PURE__ */
|
|
278
|
+
}), /* @__PURE__ */ w("div", {
|
|
276
279
|
className: "mt-1 text-sm font-semibold text-text-primary",
|
|
277
|
-
children:
|
|
280
|
+
children: F.processingFee ?? "—"
|
|
278
281
|
})]
|
|
279
282
|
}),
|
|
280
|
-
/* @__PURE__ */
|
|
283
|
+
/* @__PURE__ */ T("div", {
|
|
281
284
|
className: "rounded-lg bg-bg-sunken/50 p-4",
|
|
282
|
-
children: [/* @__PURE__ */
|
|
285
|
+
children: [/* @__PURE__ */ w("div", {
|
|
283
286
|
className: "text-xs text-text-secondary",
|
|
284
287
|
children: "Prepayment"
|
|
285
|
-
}), /* @__PURE__ */
|
|
286
|
-
className: e("mt-1 text-sm font-semibold",
|
|
287
|
-
children:
|
|
288
|
+
}), /* @__PURE__ */ w("div", {
|
|
289
|
+
className: e("mt-1 text-sm font-semibold", F.prepaymentCharges === "None" ? "text-status-success-text" : "text-text-primary"),
|
|
290
|
+
children: F.prepaymentCharges ?? "—"
|
|
288
291
|
})]
|
|
289
292
|
})
|
|
290
293
|
]
|
|
291
294
|
})]
|
|
292
|
-
}),
|
|
295
|
+
}), q.length > 0 && /* @__PURE__ */ T(O, {
|
|
293
296
|
treatment: "plain",
|
|
294
297
|
className: "border-border-seam p-6",
|
|
295
|
-
children: [/* @__PURE__ */
|
|
298
|
+
children: [/* @__PURE__ */ w("h2", {
|
|
296
299
|
className: "mb-4 text-base font-semibold text-text-primary",
|
|
297
300
|
children: "Features"
|
|
298
|
-
}), /* @__PURE__ */
|
|
301
|
+
}), /* @__PURE__ */ w("ul", {
|
|
299
302
|
className: "grid grid-cols-1 gap-2 sm:grid-cols-2",
|
|
300
|
-
children:
|
|
303
|
+
children: q.map((e, t) => /* @__PURE__ */ T("li", {
|
|
301
304
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
302
|
-
children: [/* @__PURE__ */
|
|
305
|
+
children: [/* @__PURE__ */ w(g, { className: "h-4 w-4 shrink-0 text-status-success-text" }), e]
|
|
303
306
|
}, t))
|
|
304
307
|
})]
|
|
305
308
|
})]
|
|
306
|
-
}), /* @__PURE__ */
|
|
309
|
+
}), /* @__PURE__ */ w("div", {
|
|
307
310
|
className: "space-y-6",
|
|
308
|
-
children: /* @__PURE__ */
|
|
311
|
+
children: /* @__PURE__ */ T(O, {
|
|
309
312
|
treatment: "plain",
|
|
310
313
|
className: "border-border-seam p-5",
|
|
311
314
|
children: [
|
|
312
|
-
/* @__PURE__ */
|
|
315
|
+
/* @__PURE__ */ w("h3", {
|
|
313
316
|
className: "mb-2 text-base font-semibold text-text-primary",
|
|
314
317
|
children: "Ready to apply?"
|
|
315
318
|
}),
|
|
316
|
-
/* @__PURE__ */
|
|
319
|
+
/* @__PURE__ */ T("p", {
|
|
317
320
|
className: "mb-4 text-sm text-text-secondary",
|
|
318
321
|
children: [
|
|
319
322
|
"Applications are handled directly with ",
|
|
320
|
-
|
|
323
|
+
F.bankName,
|
|
321
324
|
" through HousingVista Messages. Start an enquiry and the bank will guide you through eligibility and paperwork."
|
|
322
325
|
]
|
|
323
326
|
}),
|
|
324
|
-
/* @__PURE__ */
|
|
327
|
+
/* @__PURE__ */ w(E, {
|
|
325
328
|
type: "button",
|
|
326
329
|
variant: "default",
|
|
327
330
|
className: "w-full justify-center",
|
|
328
|
-
onClick: () =>
|
|
331
|
+
onClick: () => H(!0),
|
|
329
332
|
children: "Apply Now"
|
|
330
333
|
})
|
|
331
334
|
]
|
|
332
335
|
})
|
|
333
336
|
})]
|
|
334
337
|
}),
|
|
335
|
-
/* @__PURE__ */
|
|
336
|
-
open:
|
|
337
|
-
onOpenChange:
|
|
338
|
-
entityId:
|
|
338
|
+
/* @__PURE__ */ w(l, {
|
|
339
|
+
open: V,
|
|
340
|
+
onOpenChange: H,
|
|
341
|
+
entityId: F.id,
|
|
339
342
|
entityType: "housingvista-home-loan",
|
|
340
|
-
title:
|
|
341
|
-
pagePath:
|
|
342
|
-
heading: `Apply with ${
|
|
343
|
-
description: `Start your application with ${
|
|
343
|
+
title: F.bankName,
|
|
344
|
+
pagePath: P.pathname,
|
|
345
|
+
heading: `Apply with ${F.bankName}`,
|
|
346
|
+
description: `Start your application with ${F.bankName}. Messages are saved and delivered in your inbox.`
|
|
344
347
|
})
|
|
345
348
|
]
|
|
346
349
|
});
|
|
347
350
|
}
|
|
348
351
|
//#endregion
|
|
349
|
-
export {
|
|
352
|
+
export { N as HomeLoanDetailPage };
|
|
350
353
|
|
|
351
354
|
//# sourceMappingURL=HomeLoanDetailPage.js.map
|