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 <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-15 11:23:34 +07:00
parent 20b79acf08
commit eebe24e1ae
4 changed files with 40 additions and 2 deletions

View File

@@ -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) => {

View File

@@ -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 (
<div className="mx-auto max-w-6xl px-4 py-6">
@@ -201,13 +203,21 @@ export function ListingDetailClient({ listing }: ListingDetailClientProps) {
Gọi ngay
</Button>
</a>
<Button variant="outline" className="w-full gap-2">
<Button variant="outline" className="w-full gap-2" onClick={() => setInquiryOpen(true)}>
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
Nhắn tin
</Button>
<InquiryModal
open={inquiryOpen}
onOpenChange={setInquiryOpen}
listingId={listing.id}
listingTitle={property.title}
sellerName={seller.fullName}
/>
{agent && (
<div className="border-t pt-3">
<p className="text-xs text-muted-foreground">Môi giới</p>

View File

@@ -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, {}),
});
},
});
}

View File

@@ -15,6 +15,12 @@ export interface InquiryReadDto {
createdAt: string;
}
export interface CreateInquiryDto {
listingId: string;
message: string;
phone: string;
}
export interface PaginatedResult<T> {
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<InquiryReadDto>('/inquiries', data),
};