task-ownership.ts2.5 KBView on GitHub import { useViewerUserId } from '@/modules/auth/hooks/use-viewer-user-id';
import { useMemo } from 'react';
export interface TaskOwner {
id: string;
name?: string | null;
email?: string | null;
image?: string | null;
}
/** Anything with an owner — every task shape the conversation surfaces render. */
interface OwnedTask {
owner?: TaskOwner | null;
}
/**
* The viewer's user id, or null while it isn't positively known.
*
* The session resolves asynchronously and yields nothing on the server render, so callers
* must treat null as "can't tell whose task this is yet" and fall back to the neutral answer
* rather than guessing — see the fail-closed note on `useForeignTaskOwner`.
*
* A thin alias over `useViewerUserId` so this module keeps its own vocabulary while the rule
* for WHO the viewer is stays in one place: under Cedar-staff admin view it is the
* impersonated user, so that person's own follow-ups do not all read as a teammate's.
*/
export function useViewerId(): string | null {
return useViewerUserId();
}
/**
* Split a task list into the viewer's own tasks and their teammates'.
*
* A conversation is shared across the org and so is its task list, so a deal that two people
* work shows both people's follow-ups in one column. Interleaved, that column stops answering
* the question it exists for — "what is on me?" — because a teammate's task looks exactly like
* one of yours that you've forgotten, and you can't act on it anyway (every task mutation is
* owner-scoped server side).
*
* Fails closed like the owner badge: with no viewer id, or a task whose owner is unattributable,
* the task counts as the viewer's. A wrong guess demotes your own work to the bottom of the
* page under someone else's name, which is worse than not separating at all.
*/
export function partitionTasksByOwner<T extends OwnedTask>(
tasks: T[],
viewerId: string | null | undefined,
): { mine: T[]; theirs: T[] } {
if (!viewerId) return { mine: tasks, theirs: [] };
const mine: T[] = [];
const theirs: T[] = [];
for (const task of tasks) {
if (task.owner?.id && task.owner.id !== viewerId) theirs.push(task);
else mine.push(task);
}
return { mine, theirs };
}
/** `partitionTasksByOwner` bound to the signed-in viewer. */
export function useTasksByOwnership<T extends OwnedTask>(tasks: T[]): { mine: T[]; theirs: T[] } {
const viewerId = useViewerId();
return useMemo(() => partitionTasksByOwner(tasks, viewerId), [tasks, viewerId]);
}