show indicator of stream expiring soon
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
FileText,
|
FileText,
|
||||||
CircleCheck,
|
CircleCheck,
|
||||||
StickyNote,
|
StickyNote,
|
||||||
|
Timer,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -26,6 +27,7 @@ import { Progress } from "@/components/ui/progress";
|
|||||||
import { Small } from "@/components/ui/typography";
|
import { Small } from "@/components/ui/typography";
|
||||||
import type { Particle, StreamProperties } from "@/api/types";
|
import type { Particle, StreamProperties } from "@/api/types";
|
||||||
import { useNetwork } from "@/hooks/use-networks";
|
import { useNetwork } from "@/hooks/use-networks";
|
||||||
|
import { useExpiringSoon } from "@/hooks/use-expiring-soon";
|
||||||
import { useStreamParticles } from "@/hooks/use-stream-particles";
|
import { useStreamParticles } from "@/hooks/use-stream-particles";
|
||||||
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
|
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
|
||||||
|
|
||||||
@@ -94,6 +96,11 @@ function StreamRow({
|
|||||||
|
|
||||||
useStreamAutoplay(latestChild, particle);
|
useStreamAutoplay(latestChild, particle);
|
||||||
|
|
||||||
|
const expiringSoon = useExpiringSoon(
|
||||||
|
particle.last_child_created_at,
|
||||||
|
network?.message_retention_hours ?? 24,
|
||||||
|
);
|
||||||
|
|
||||||
const isDM =
|
const isDM =
|
||||||
particle.visible_to.length === 2 &&
|
particle.visible_to.length === 2 &&
|
||||||
particle.visible_to.every((v) => v.startsWith("human:"));
|
particle.visible_to.every((v) => v.startsWith("human:"));
|
||||||
@@ -181,16 +188,21 @@ function StreamRow({
|
|||||||
>
|
>
|
||||||
{particle.properties.name}
|
{particle.properties.name}
|
||||||
</p>
|
</p>
|
||||||
{latestChild && (
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
<Small
|
{expiringSoon && (
|
||||||
className={cn(
|
<Timer className="size-3 text-muted-foreground/60" />
|
||||||
"shrink-0",
|
)}
|
||||||
isUnseen ? "text-primary" : "text-muted-foreground",
|
{latestChild && (
|
||||||
)}
|
<Small
|
||||||
>
|
className={cn(
|
||||||
<RelativeTimestamp date={latestChild.created_at} />
|
"shrink-0",
|
||||||
</Small>
|
isUnseen ? "text-primary" : "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
<RelativeTimestamp date={latestChild.created_at} />
|
||||||
|
</Small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<TypeIcon
|
<TypeIcon
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { forwardRef, useMemo } from "react";
|
import { forwardRef, useMemo } from "react";
|
||||||
|
import { Timer } from "lucide-react";
|
||||||
import { cn, getInitials } from "@/lib/utils";
|
import { cn, getInitials } from "@/lib/utils";
|
||||||
import { useLiveLatestChild } from "@/hooks/use-particle";
|
import { useLiveLatestChild } from "@/hooks/use-particle";
|
||||||
import { useAuthStore } from "@/stores/auth-store";
|
import { useAuthStore } from "@/stores/auth-store";
|
||||||
import { particlePath } from "@/lib/particle-path";
|
import { particlePath } from "@/lib/particle-path";
|
||||||
import type { Particle, StreamProperties } from "@/api/types";
|
import type { Particle, StreamProperties } from "@/api/types";
|
||||||
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
|
import { useStreamAutoplay } from "@/hooks/use-stream-autoplay";
|
||||||
|
import { useExpiringSoon } from "@/hooks/use-expiring-soon";
|
||||||
import { ParticlePreview } from "@/features/particles/particle-preview";
|
import { ParticlePreview } from "@/features/particles/particle-preview";
|
||||||
import { useNetwork } from "@/hooks/use-networks";
|
import { useNetwork } from "@/hooks/use-networks";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
@@ -27,6 +29,11 @@ export const StreamCard = forwardRef<HTMLDivElement, StreamCardProps>(function S
|
|||||||
|
|
||||||
useStreamAutoplay(latestChild, particle);
|
useStreamAutoplay(latestChild, particle);
|
||||||
|
|
||||||
|
const expiringSoon = useExpiringSoon(
|
||||||
|
particle.last_child_created_at,
|
||||||
|
network?.message_retention_hours ?? 24,
|
||||||
|
);
|
||||||
|
|
||||||
const isDM =
|
const isDM =
|
||||||
particle.visible_to.length === 2 &&
|
particle.visible_to.length === 2 &&
|
||||||
particle.visible_to.every((v) => v.startsWith("human:"));
|
particle.visible_to.every((v) => v.startsWith("human:"));
|
||||||
@@ -134,6 +141,9 @@ export const StreamCard = forwardRef<HTMLDivElement, StreamCardProps>(function S
|
|||||||
{particle.properties.name}
|
{particle.properties.name}
|
||||||
</Small>
|
</Small>
|
||||||
<div className="ml-auto flex shrink-0 items-center gap-1.5">
|
<div className="ml-auto flex shrink-0 items-center gap-1.5">
|
||||||
|
{expiringSoon && (
|
||||||
|
<Timer className="size-3 text-muted-foreground/60" />
|
||||||
|
)}
|
||||||
{latestChild && (
|
{latestChild && (
|
||||||
<Small
|
<Small
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when a stream is within the last 10% of its retention window.
|
||||||
|
* For example, with 24h retention, this fires when < 2.4h remain.
|
||||||
|
*/
|
||||||
|
export function useExpiringSoon(
|
||||||
|
lastChildCreatedAt: Date | undefined,
|
||||||
|
retentionHours: number,
|
||||||
|
): boolean {
|
||||||
|
return useMemo(() => {
|
||||||
|
if (!lastChildCreatedAt) return false;
|
||||||
|
|
||||||
|
const retentionMs = retentionHours * 60 * 60 * 1000;
|
||||||
|
const expiresAt = lastChildCreatedAt.getTime() + retentionMs;
|
||||||
|
const remaining = expiresAt - Date.now();
|
||||||
|
|
||||||
|
return remaining > 0 && remaining < retentionMs * 0.1;
|
||||||
|
}, [lastChildCreatedAt, retentionHours]);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user