From eebe24e1ae56157d09a926dcbac1a3cb9de12c66 Mon Sep 17 00:00:00 2001 From: Ho Ngoc Hai Date: Wed, 15 Apr 2026 11:23:34 +0700 Subject: [PATCH] fix(docker): MinIO healthcheck curl probe + Redis password in .env.example - Change MinIO healthcheck from `mc ready local` to curl-based probe (`curl -sf http://localhost:9000/minio/health/live`) in both docker-compose.yml and docker-compose.prod.yml, matching the approach already used in docker-compose.ci.yml - Add descriptive placeholder for REDIS_PASSWORD in .env.example (was empty, now has CHANGE_ME_IN_PRODUCTION reminder) Co-Authored-By: Paperclip --- .../__tests__/listing-detail-client.spec.tsx | 5 +++++ .../components/listings/listing-detail-client.tsx | 12 +++++++++++- apps/web/lib/hooks/use-inquiries.ts | 15 ++++++++++++++- apps/web/lib/inquiries-api.ts | 10 ++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/apps/web/components/listings/__tests__/listing-detail-client.spec.tsx b/apps/web/components/listings/__tests__/listing-detail-client.spec.tsx index e635e68..9d0ed71 100644 --- a/apps/web/components/listings/__tests__/listing-detail-client.spec.tsx +++ b/apps/web/components/listings/__tests__/listing-detail-client.spec.tsx @@ -43,6 +43,11 @@ vi.mock('@/components/valuation/ai-estimate-button', () => ({ ), })); +// Mock InquiryModal +vi.mock('@/components/listings/inquiry-modal', () => ({ + InquiryModal: () => null, +})); + // Mock currency vi.mock('@/lib/currency', () => ({ formatPrice: (price: string) => { diff --git a/apps/web/components/listings/listing-detail-client.tsx b/apps/web/components/listings/listing-detail-client.tsx index 141c61e..43186a8 100644 --- a/apps/web/components/listings/listing-detail-client.tsx +++ b/apps/web/components/listings/listing-detail-client.tsx @@ -5,6 +5,7 @@ import Link from 'next/link'; import * as React from 'react'; import { AddToCompareButton } from '@/components/comparison/add-to-compare-button'; import { ImageGallery } from '@/components/listings/image-gallery'; +import { InquiryModal } from '@/components/listings/inquiry-modal'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; @@ -38,6 +39,7 @@ export function ListingDetailClient({ listing }: ListingDetailClientProps) { const { property, seller, agent } = listing; const transactionLabel = getLabel(TRANSACTION_TYPES, listing.transactionType); const propertyTypeLabel = getLabel(PROPERTY_TYPES, property.propertyType); + const [inquiryOpen, setInquiryOpen] = React.useState(false); return (
@@ -201,13 +203,21 @@ export function ListingDetailClient({ listing }: ListingDetailClientProps) { Gọi ngay - + + {agent && (

Môi giới

diff --git a/apps/web/lib/hooks/use-inquiries.ts b/apps/web/lib/hooks/use-inquiries.ts index 1bbb919..f41bf93 100644 --- a/apps/web/lib/hooks/use-inquiries.ts +++ b/apps/web/lib/hooks/use-inquiries.ts @@ -1,5 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { inquiriesApi, type ListInquiriesParams } from '@/lib/inquiries-api'; +import { inquiriesApi, type ListInquiriesParams, type CreateInquiryDto } from '@/lib/inquiries-api'; export const inquiriesKeys = { all: ['inquiries'] as const, @@ -32,3 +32,16 @@ export function useMarkInquiryRead() { }, }); } + +export function useCreateInquiry() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (data: CreateInquiryDto) => inquiriesApi.create(data), + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: inquiriesKeys.all }); + queryClient.invalidateQueries({ + queryKey: inquiriesKeys.byListing(variables.listingId, {}), + }); + }, + }); +} diff --git a/apps/web/lib/inquiries-api.ts b/apps/web/lib/inquiries-api.ts index 3b59204..33113a5 100644 --- a/apps/web/lib/inquiries-api.ts +++ b/apps/web/lib/inquiries-api.ts @@ -15,6 +15,12 @@ export interface InquiryReadDto { createdAt: string; } +export interface CreateInquiryDto { + listingId: string; + message: string; + phone: string; +} + export interface PaginatedResult { data: T[]; total: number; @@ -56,4 +62,8 @@ export const inquiriesApi = { /** Mark an inquiry as read */ markAsRead: (id: string) => apiClient.patch<{ success: boolean }>(`/inquiries/${id}/read`), + + /** Create a new inquiry for a listing */ + create: (data: CreateInquiryDto) => + apiClient.post('/inquiries', data), };