// Copyright: © 2026 TWWIM UG. All rights reserved. (www.twwim.com)
/**
* OperatorAvailabilityToggle — pill-shaped switch in the Comm Center header.
*
* - green dot + "I'm available" when isAvailable=true
* - grey dot + "I'm away" when isAvailable=false
* - neutral pill ("Not an operator") when the API returns 403 (caller is
* ADMIN / MANAGER / WORKER — they have no presence state to flip)
* - spinner while a mutation is in flight
*
* Click flips the value via PATCH /operator/availability. Persistence lives
* in customer_data.company_membership.is_available; presence is the only
* thing this toggle moves — routing logic (AI vs operator) is unchanged.
*/
import { Loader2 } from 'lucide-react';
import {
useOperatorAvailability,
useSetOperatorAvailability,
} from '../hooks/useOperatorAvailability';
interface ApiLikeError {
status?: number;
response?: { status?: number };
}
function statusOf(err: unknown): number | undefined {
const candidate = err as ApiLikeError | null;
return candidate?.status ?? candidate?.response?.status;
}
export function OperatorAvailabilityToggle() {
const query = useOperatorAvailability();
const mutation = useSetOperatorAvailability();
// 403 → caller is not an OPERATOR; render a neutral disabled pill so the UI
// doesn't lie about a non-existent presence flag.
if (statusOf(query.error) === 403) {
return (
Not an operator
);
}
if (query.isLoading) {
return (
Loading…
);
}
if (query.isError || !query.data) {
return (
Availability unavailable
);
}
const { isAvailable } = query.data;
const onToggle = () => {
if (mutation.isPending) return;
mutation.mutate({ isAvailable: !isAvailable });
};
const palette = isAvailable
? 'bg-emerald-50 text-emerald-700 ring-emerald-200 hover:bg-emerald-100'
: 'bg-gray-50 text-gray-600 ring-gray-200 hover:bg-gray-100';
const dot = isAvailable
? 'bg-emerald-500 animate-pulse'
: 'bg-gray-400';
return (
);
}