@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ArrowLeft, Mail, Shield, X } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
@@ -8,14 +8,17 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { Muted } from "@/components/ui/typography";
|
||||
import { WindowControls } from "@/components/window-controls";
|
||||
import { useNetworks } from "@/hooks/use-networks";
|
||||
import { useSetMessageRetention } from "@/hooks/use-network-settings";
|
||||
import {
|
||||
useNetworkInvitations,
|
||||
useInviteMembers,
|
||||
useRevokeInvitation,
|
||||
} from "@/hooks/use-invitations";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
import type { Human } from "@/api/types";
|
||||
|
||||
function MemberRow({
|
||||
@@ -147,12 +150,62 @@ function SettingsGroup({
|
||||
);
|
||||
}
|
||||
|
||||
function formatRetentionDays(hours: number): string {
|
||||
const days = Math.round(hours / 24);
|
||||
return days === 1 ? "1 day" : `${days} days`;
|
||||
}
|
||||
|
||||
function EphemeralitySettings({ networkId, retentionHours }: { networkId: string; retentionHours: number }) {
|
||||
const setRetention = useSetMessageRetention(networkId);
|
||||
const [days, setDays] = useState(Math.round(retentionHours / 24));
|
||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
// Sync local state if server value changes externally
|
||||
useEffect(() => {
|
||||
setDays(Math.round(retentionHours / 24));
|
||||
}, [retentionHours]);
|
||||
|
||||
const handleChange = useCallback((value: number[]) => {
|
||||
const newDays = value[0];
|
||||
setDays(newDays);
|
||||
|
||||
if (debounceRef.current) clearTimeout(debounceRef.current);
|
||||
debounceRef.current = setTimeout(() => {
|
||||
setRetention.mutate(newDays * 24, {
|
||||
onSuccess: () => toast.success("Retention window updated"),
|
||||
onError: (err) => toast.error(err.message || "Failed to update retention"),
|
||||
});
|
||||
}, 500);
|
||||
}, [setRetention]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 px-4 py-3">
|
||||
<div className="flex items-baseline justify-between">
|
||||
<p className="text-sm font-medium">Messages disappear after</p>
|
||||
<p className="text-sm font-semibold">{formatRetentionDays(days * 24)}</p>
|
||||
</div>
|
||||
<Slider
|
||||
min={1}
|
||||
max={14}
|
||||
step={1}
|
||||
value={[days]}
|
||||
onValueChange={handleChange}
|
||||
/>
|
||||
<Muted className="text-xs">
|
||||
Older messages are no longer visible to anyone.
|
||||
</Muted>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function NetworkSettingsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { networkId } = useParams<{ networkId: string }>();
|
||||
const { data: networks } = useNetworks();
|
||||
const network = networks?.find((n) => n.id === networkId);
|
||||
const { data: invitations } = useNetworkInvitations(networkId!);
|
||||
const currentUser = useAuthStore((s) => s.user);
|
||||
const isAdmin = currentUser?.id === network?.admin_human.id;
|
||||
|
||||
const networkName = network?.name ?? "Network";
|
||||
|
||||
@@ -187,33 +240,49 @@ export default function NetworkSettingsPage() {
|
||||
))}
|
||||
</SettingsGroup>
|
||||
|
||||
<Separator className="mt-4" />
|
||||
|
||||
<SettingsGroup title="Invite">
|
||||
<InviteForm networkId={networkId!} />
|
||||
</SettingsGroup>
|
||||
{isAdmin && network && (
|
||||
<>
|
||||
<Separator className="mt-4" />
|
||||
<SettingsGroup title="Ephemerality">
|
||||
<EphemeralitySettings
|
||||
networkId={networkId!}
|
||||
retentionHours={network.message_retention_hours}
|
||||
/>
|
||||
</SettingsGroup>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Separator className="mt-4" />
|
||||
|
||||
<SettingsGroup title="Pending Invitations">
|
||||
{invitations && invitations.length > 0 ? (
|
||||
invitations.map((inv, index) => (
|
||||
<div key={inv.email}>
|
||||
<PendingInvitationRow
|
||||
email={inv.email}
|
||||
networkId={networkId!}
|
||||
/>
|
||||
{index < invitations.length - 1 && (
|
||||
<Separator className="mx-4" />
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-muted-foreground px-4 py-3 text-sm">
|
||||
No pending invitations
|
||||
</p>
|
||||
)}
|
||||
</SettingsGroup>
|
||||
{isAdmin && network && (
|
||||
<>
|
||||
<SettingsGroup title="Invite">
|
||||
<InviteForm networkId={networkId!} />
|
||||
</SettingsGroup>
|
||||
|
||||
<Separator className="mt-4" />
|
||||
|
||||
<SettingsGroup title="Pending Invitations">
|
||||
{invitations && invitations.length > 0 ? (
|
||||
invitations.map((inv, index) => (
|
||||
<div key={inv.email}>
|
||||
<PendingInvitationRow
|
||||
email={inv.email}
|
||||
networkId={networkId!}
|
||||
/>
|
||||
{index < invitations.length - 1 && (
|
||||
<Separator className="mx-4" />
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<p className="text-muted-foreground px-4 py-3 text-sm">
|
||||
No pending invitations
|
||||
</p>
|
||||
)}
|
||||
</SettingsGroup>
|
||||
</>
|
||||
)}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user