feat: Implement agent availability status with a toggle for agents and display on user profiles.
This commit is contained in:
@@ -109,6 +109,8 @@ export default function AgentDashboard() {
|
|||||||
const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false);
|
const [isPhotoModalOpen, setIsPhotoModalOpen] = useState(false);
|
||||||
const [imageError, setImageError] = useState(false);
|
const [imageError, setImageError] = useState(false);
|
||||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
||||||
|
const [isAvailable, setIsAvailable] = useState(true);
|
||||||
|
const [isTogglingAvailability, setIsTogglingAvailability] = useState(false);
|
||||||
|
|
||||||
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
|
const defaultImage = '/assets/demo_images/8f017153a7b4a239a4f0691234ef97dd55092282.jpg';
|
||||||
|
|
||||||
@@ -134,6 +136,7 @@ export default function AgentDashboard() {
|
|||||||
|
|
||||||
setAgentProfile(profile);
|
setAgentProfile(profile);
|
||||||
setFieldValues(fieldValuesResponse.fieldValues);
|
setFieldValues(fieldValuesResponse.fieldValues);
|
||||||
|
setIsAvailable(profile.isAvailable ?? true);
|
||||||
|
|
||||||
// Map field values to experience data structure
|
// Map field values to experience data structure
|
||||||
const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues);
|
const mappedExperience = mapFieldValuesToExperience(fieldValuesResponse.fieldValues);
|
||||||
@@ -175,6 +178,28 @@ export default function AgentDashboard() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Handle availability toggle
|
||||||
|
const handleToggleAvailability = async () => {
|
||||||
|
if (isTogglingAvailability) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsTogglingAvailability(true);
|
||||||
|
const newAvailability = !isAvailable;
|
||||||
|
|
||||||
|
// Update on server
|
||||||
|
const updatedProfile = await agentsService.updateProfile({ isAvailable: newAvailability });
|
||||||
|
|
||||||
|
// Update local state
|
||||||
|
setIsAvailable(newAvailability);
|
||||||
|
setAgentProfile(updatedProfile);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to update availability:', err);
|
||||||
|
// Revert on error - state stays the same
|
||||||
|
} finally {
|
||||||
|
setIsTogglingAvailability(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handlePhotoUpload = async (file: File) => {
|
const handlePhotoUpload = async (file: File) => {
|
||||||
// Upload avatar to S3 (uses agent ID as filename for auto-replacement)
|
// Upload avatar to S3 (uses agent ID as filename for auto-replacement)
|
||||||
const uploadedFile = await uploadService.uploadAvatar(file);
|
const uploadedFile = await uploadService.uploadAvatar(file);
|
||||||
@@ -300,8 +325,12 @@ export default function AgentDashboard() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Buttons */}
|
{/* Status Buttons - Toggle for agent to set availability */}
|
||||||
<StatusButtons />
|
<StatusButtons
|
||||||
|
isAvailable={isAvailable}
|
||||||
|
showToggle={true}
|
||||||
|
onToggleAvailability={handleToggleAvailability}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Contact Info */}
|
{/* Contact Info */}
|
||||||
<ContactInfo
|
<ContactInfo
|
||||||
|
|||||||
@@ -208,8 +208,8 @@ export default function AgentProfileView() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Status Buttons */}
|
{/* Status Buttons - Public view shows availability status */}
|
||||||
<StatusButtons />
|
<StatusButtons isAvailable={agentProfile.isAvailable ?? true} />
|
||||||
|
|
||||||
{/* Contact Info */}
|
{/* Contact Info */}
|
||||||
<ContactInfo
|
<ContactInfo
|
||||||
|
|||||||
@@ -5,15 +5,23 @@ import { useRouter, usePathname } from 'next/navigation';
|
|||||||
|
|
||||||
interface StatusButtonsProps {
|
interface StatusButtonsProps {
|
||||||
isAvailable?: boolean;
|
isAvailable?: boolean;
|
||||||
|
onToggleAvailability?: () => void;
|
||||||
|
showToggle?: boolean; // If true, show as toggle (for agent's own dashboard)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function StatusButtons({ isAvailable = true }: StatusButtonsProps) {
|
export function StatusButtons({
|
||||||
|
isAvailable = true,
|
||||||
|
onToggleAvailability,
|
||||||
|
showToggle = false
|
||||||
|
}: StatusButtonsProps) {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
// Handle connect click - require login if not authenticated
|
// Handle connect click - require login if not authenticated
|
||||||
const handleConnectClick = () => {
|
const handleConnectClick = () => {
|
||||||
|
if (!isAvailable) return; // Don't do anything if unavailable
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
// Store the current page to redirect back after login
|
// Store the current page to redirect back after login
|
||||||
const returnUrl = encodeURIComponent(pathname);
|
const returnUrl = encodeURIComponent(pathname);
|
||||||
@@ -23,28 +31,73 @@ export function StatusButtons({ isAvailable = true }: StatusButtonsProps) {
|
|||||||
// TODO: Handle connect action when logged in
|
// TODO: Handle connect action when logged in
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
// If showToggle is true, render both options as a toggle selector (for agent dashboard)
|
||||||
<div className="w-full max-w-[354px] lg:max-w-none space-y-2">
|
if (showToggle) {
|
||||||
<div className="flex items-center border border-[#00293d]/10 rounded-[15px] h-[50px] px-4">
|
return (
|
||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="w-full max-w-[354px] lg:max-w-none space-y-2">
|
||||||
<span className="w-3 h-3 bg-green-500 rounded-full" />
|
{/* Available Option */}
|
||||||
<span className="text-sm font-bold text-[#00293d] font-fractul">Available.</span>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
onClick={handleConnectClick}
|
onClick={() => !isAvailable && onToggleAvailability?.()}
|
||||||
className="px-5 py-1.5 bg-[#e58625] text-white text-sm rounded-[15px] hover:bg-[#d47720] transition-colors font-fractul"
|
className={`w-full flex items-center border rounded-[15px] h-[50px] px-4 transition-colors ${
|
||||||
|
isAvailable
|
||||||
|
? 'border-green-500 bg-green-50'
|
||||||
|
: 'border-[#00293d]/10 hover:border-[#00293d]/20'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Connect
|
<div className="flex items-center gap-2 flex-1">
|
||||||
|
<span className={`w-3 h-3 rounded-full ${
|
||||||
|
isAvailable ? 'bg-green-500' : 'bg-white border border-gray-400'
|
||||||
|
}`} />
|
||||||
|
<span className="text-sm font-bold text-[#00293d] font-fractul">Available.</span>
|
||||||
|
</div>
|
||||||
|
{isAvailable && (
|
||||||
|
<span className="text-xs text-green-600 font-medium">Active</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Unavailable Option */}
|
||||||
|
<button
|
||||||
|
onClick={() => isAvailable && onToggleAvailability?.()}
|
||||||
|
className={`w-full flex items-center border rounded-[15px] h-[50px] px-4 transition-colors ${
|
||||||
|
!isAvailable
|
||||||
|
? 'border-[#00293d] bg-[#00293d]/5'
|
||||||
|
: 'border-[#00293d]/10 hover:border-[#00293d]/20'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 flex-1">
|
||||||
|
<span className={`w-3 h-3 rounded-full ${
|
||||||
|
!isAvailable ? 'bg-[#00293d]' : 'bg-white border border-gray-400'
|
||||||
|
}`} />
|
||||||
|
<span className="text-sm font-bold text-[#00293d] font-fractul">Unavailable.</span>
|
||||||
|
</div>
|
||||||
|
{!isAvailable && (
|
||||||
|
<span className="text-xs text-[#00293d]/70 font-medium">Active</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public view - show single status with Connect button
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-[354px] lg:max-w-none">
|
||||||
<div className="flex items-center border border-[#00293d]/10 rounded-[15px] h-[50px] px-4">
|
<div className="flex items-center border border-[#00293d]/10 rounded-[15px] h-[50px] px-4">
|
||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<span className="w-3 h-3 bg-white border border-gray-400 rounded-full" />
|
<span className={`w-3 h-3 rounded-full ${
|
||||||
<span className="text-sm font-bold text-[#00293d] font-fractul">Unavailable.</span>
|
isAvailable ? 'bg-green-500' : 'bg-white border border-gray-400'
|
||||||
|
}`} />
|
||||||
|
<span className="text-sm font-bold text-[#00293d] font-fractul">
|
||||||
|
{isAvailable ? 'Available.' : 'Unavailable.'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleConnectClick}
|
onClick={handleConnectClick}
|
||||||
className="px-5 py-1.5 bg-[#00293d] text-white text-sm rounded-[15px] hover:bg-[#001d2b] transition-colors font-fractul"
|
disabled={!isAvailable}
|
||||||
|
className={`px-5 py-1.5 text-white text-sm rounded-[15px] transition-colors font-fractul ${
|
||||||
|
isAvailable
|
||||||
|
? 'bg-[#e58625] hover:bg-[#d47720] cursor-pointer'
|
||||||
|
: 'bg-[#00293d] cursor-not-allowed opacity-70'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
Connect
|
Connect
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface AgentProfile {
|
|||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
isFeatured: boolean;
|
isFeatured: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
isAvailable: boolean;
|
||||||
agentTypeId: string | null;
|
agentTypeId: string | null;
|
||||||
agentType: AgentType | null;
|
agentType: AgentType | null;
|
||||||
user?: {
|
user?: {
|
||||||
@@ -121,6 +122,7 @@ export interface PublicAgentProfile {
|
|||||||
reviewCount: number;
|
reviewCount: number;
|
||||||
isVerified: boolean;
|
isVerified: boolean;
|
||||||
isFeatured: boolean;
|
isFeatured: boolean;
|
||||||
|
isAvailable: boolean;
|
||||||
agentTypeId: string | null;
|
agentTypeId: string | null;
|
||||||
agentType: AgentType | null;
|
agentType: AgentType | null;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user