feat: Implement agent-specific contact page CTA, enhance avatar loading and message previews, and refine messaging read receipts with auto-mark functionality.
This commit is contained in:
@@ -35,10 +35,14 @@ export function ConnectionCard({
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4">
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
|
||||
{(!avatar || !avatarLoaded || isPlaceholder) && (
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
||||
{isPlaceholder ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-[#c4d9d4]">
|
||||
<Image src={placeholder} alt={name} width={40} height={40} className="opacity-50" />
|
||||
</div>
|
||||
) : !avatarLoaded ? (
|
||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
||||
)}
|
||||
) : null}
|
||||
{avatar && !isPlaceholder && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
|
||||
@@ -49,10 +49,14 @@ export function InvitationCard({
|
||||
<div className={`flex flex-col sm:flex-row items-start sm:items-center gap-4 py-4 ${isProcessing ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative">
|
||||
{(!avatar || !avatarLoaded || isPlaceholder) && (
|
||||
<div className="w-[80px] h-[80px] rounded-full overflow-hidden relative bg-[#e8e8e8]">
|
||||
{isPlaceholder ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-[#c4d9d4]">
|
||||
<Image src={placeholder} alt={name} width={40} height={40} className="opacity-50" />
|
||||
</div>
|
||||
) : !avatarLoaded ? (
|
||||
<div className="absolute inset-0 rounded-full shimmer-loading" />
|
||||
)}
|
||||
) : null}
|
||||
{avatar && !isPlaceholder && (
|
||||
<img
|
||||
ref={(el) => { if (el?.complete && el.naturalWidth > 0) setAvatarLoaded(true); }}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { CommonHeader } from '@/components/layout/CommonHeader';
|
||||
import { Footer } from '@/components/layout/Footer';
|
||||
import { cmsService } from '@/services/cms.service';
|
||||
@@ -47,6 +48,9 @@ const defaultCta: ContactCta = {
|
||||
};
|
||||
|
||||
export default function ContactPage() {
|
||||
const { data: session } = useSession();
|
||||
const isAgent = (session?.user as any)?.role === 'AGENT';
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
phone: '',
|
||||
@@ -345,21 +349,21 @@ export default function ContactPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CTA Section - Ready to find an agent */}
|
||||
{/* CTA Section */}
|
||||
<div className="max-w-4xl mx-auto border border-[#00293d]/20 rounded-[15px] p-8 lg:p-10 mb-10">
|
||||
<div className="flex flex-col lg:flex-row items-center justify-between gap-6">
|
||||
<div className="lg:max-w-[400px]">
|
||||
<h2 className="font-fractul font-bold text-[24px] lg:text-[28px] text-[#00293d] mb-3">
|
||||
{cta.title}
|
||||
{isAgent ? 'Manage your profile' : cta.title}
|
||||
</h2>
|
||||
<p className="font-serif text-[13px] text-[#00293d] mb-5">
|
||||
{cta.description}
|
||||
{isAgent ? 'Keep your profile updated and connect with potential clients.' : cta.description}
|
||||
</p>
|
||||
<Link
|
||||
href={cta.buttonLink}
|
||||
href={isAgent ? '/agent/dashboard' : cta.buttonLink}
|
||||
className="inline-flex items-center gap-2 bg-[#e58625] text-white px-5 py-2.5 rounded-full font-fractul font-medium text-[13px] hover:bg-[#d47720] transition-colors"
|
||||
>
|
||||
{cta.buttonText}
|
||||
{isAgent ? 'Go to Dashboard' : cta.buttonText}
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12H19M19 12L12 5M19 12L12 19" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
|
||||
@@ -825,7 +825,13 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
|
||||
)}
|
||||
</div>
|
||||
<p className="font-serif font-normal text-[14px] leading-[19px] text-[#00293D] line-clamp-2">
|
||||
{conversation.lastMessageText || 'No messages yet'}
|
||||
{conversation.lastMessageText
|
||||
? /giphy\.com|\.gif(\?|$)/i.test(conversation.lastMessageText)
|
||||
? 'GIF 🎬'
|
||||
: /\.(jpg|jpeg|png|webp|svg)(\?|$)/i.test(conversation.lastMessageText) && conversation.lastMessageText.startsWith('http')
|
||||
? 'Image 📷'
|
||||
: conversation.lastMessageText
|
||||
: 'No messages yet'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -480,6 +480,17 @@ export function useMessaging(options: UseMessagingOptions = {}): UseMessagingRet
|
||||
if (prev.some((m) => m.id === message.id)) return prev;
|
||||
return [...prev, message];
|
||||
});
|
||||
|
||||
// Auto-mark as read if user is currently viewing this conversation
|
||||
// (the other user sent a message while we're looking at the chat)
|
||||
const currentUserId = (session?.user as any)?.id;
|
||||
if (message.senderId !== currentUserId) {
|
||||
if (isConnected) {
|
||||
socketService.markAsRead(message.conversationId);
|
||||
} else {
|
||||
messagesService.markAsRead(message.conversationId).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update conversation list
|
||||
@@ -536,13 +547,15 @@ export function useMessaging(options: UseMessagingOptions = {}): UseMessagingRet
|
||||
const unsubscribeRead = socketService.onMessagesRead(
|
||||
(data: MessagesReadEvent) => {
|
||||
if (data.conversationId === currentConversationIdRef.current) {
|
||||
// Update message statuses
|
||||
const currentUserId = (session?.user as any)?.id;
|
||||
// Only update status on messages SENT by current user (not received ones)
|
||||
setMessages((prev) =>
|
||||
prev.map((m) => ({
|
||||
...m,
|
||||
status: 'READ' as MessageStatus,
|
||||
readAt: data.readAt,
|
||||
}))
|
||||
prev.map((m) => {
|
||||
if (m.senderId === currentUserId && m.status !== 'READ') {
|
||||
return { ...m, status: 'READ' as MessageStatus, readAt: data.readAt };
|
||||
}
|
||||
return m;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user