diff --git a/surfsense_web/components/contact/contact-form.tsx b/surfsense_web/components/contact/contact-form.tsx index 967c1c524..03e4118de 100644 --- a/surfsense_web/components/contact/contact-form.tsx +++ b/surfsense_web/components/contact/contact-form.tsx @@ -161,7 +161,7 @@ export const FeatureIconContainer = ({ ); }; -export const Grid = ({ pattern, size }: { pattern?: number[][]; size?: number }) => { +export const Grid = ({ pattern, size }: { pattern?: [number, number][]; size?: number }) => { const p = pattern ?? [ [9, 3], [8, 5], @@ -185,7 +185,7 @@ export const Grid = ({ pattern, size }: { pattern?: number[][]; size?: number }) ); }; -export function GridPattern({ width, height, x, y, squares, ...props }: any) { +export function GridPattern({ width, height, x, y, squares, ...props }: React.ComponentProps<"svg"> & { width: number; height: number; x: string | number; y: string | number; squares?: [number, number][] }) { const patternId = useId(); return ( @@ -205,7 +205,7 @@ export function GridPattern({ width, height, x, y, squares, ...props }: any) { {squares && (
- -
- {role.description && ( -

- {role.description} -

- )} - + + -
- -
+
+ +
- {!role.is_system_role && ( -
e.stopPropagation()} - onKeyDown={(e) => e.stopPropagation()} - > - - - - - e.preventDefault()}> - {canUpdate && ( - setEditingRoleId(role.id)}> - - Edit Role - - )} - {canDelete && ( - <> - - - - e.preventDefault()}> - - Delete Role - - - - - Delete role? - - This will permanently delete the "{role.name}" role. - Members with this role will lose their permissions. - - - - Cancel - onDeleteRole(role.id)} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" - > - Delete - - - - - - )} - - -
- )} - - + {!role.is_system_role && ( +
+ + + + + e.preventDefault()}> + {canUpdate && ( + setEditingRoleId(role.id)}> + + Edit Role + + )} + {canDelete && ( + <> + + + + e.preventDefault()}> + + Delete Role + + + + + Delete role? + + This will permanently delete the "{role.name}" role. + Members with this role will lose their permissions. + + + + Cancel + onDeleteRole(role.id)} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + Delete + + + + + + )} + + +
+ )} + ))} diff --git a/surfsense_web/components/tool-ui/audio.tsx b/surfsense_web/components/tool-ui/audio.tsx index f07aa3cf6..f9634f34b 100644 --- a/surfsense_web/components/tool-ui/audio.tsx +++ b/surfsense_web/components/tool-ui/audio.tsx @@ -30,6 +30,7 @@ function formatTime(seconds: number): string { export function Audio({ id, src, title, durationMs, className }: AudioProps) { const audioRef = useRef(null); + const downloadControllerRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(durationMs ? durationMs / 1000 : 0); @@ -87,8 +88,12 @@ export function Audio({ id, src, title, durationMs, className }: AudioProps) { // Handle download const handleDownload = useCallback(async () => { + downloadControllerRef.current?.abort(); + const controller = new AbortController(); + downloadControllerRef.current = controller; + try { - const response = await fetch(src); + const response = await fetch(src, { signal: controller.signal }); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement("a"); @@ -99,10 +104,16 @@ export function Audio({ id, src, title, durationMs, className }: AudioProps) { document.body.removeChild(a); window.URL.revokeObjectURL(url); } catch (err) { + if (err instanceof DOMException && err.name === "AbortError") return; console.error("Error downloading audio:", err); } }, [src, title]); + // Abort in-flight download on unmount + useEffect(() => { + return () => downloadControllerRef.current?.abort(); + }, []); + // Set up audio event listeners useEffect(() => { const audio = audioRef.current;