feat(UI): Adding linter and prettier for UI (#3156)

This commit is contained in:
Francisco Arceo 2025-08-14 15:58:43 -06:00 committed by GitHub
parent 61582f327c
commit e69acbafbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
73 changed files with 1452 additions and 1226 deletions

View file

@ -1,85 +1,85 @@
import { useEffect, useRef, useState } from "react"
import { useEffect, useRef, useState } from "react";
import { recordAudio } from "@/lib/audio-utils"
import { recordAudio } from "@/lib/audio-utils";
interface UseAudioRecordingOptions {
transcribeAudio?: (blob: Blob) => Promise<string>
onTranscriptionComplete?: (text: string) => void
transcribeAudio?: (blob: Blob) => Promise<string>;
onTranscriptionComplete?: (text: string) => void;
}
export function useAudioRecording({
transcribeAudio,
onTranscriptionComplete,
}: UseAudioRecordingOptions) {
const [isListening, setIsListening] = useState(false)
const [isSpeechSupported, setIsSpeechSupported] = useState(!!transcribeAudio)
const [isRecording, setIsRecording] = useState(false)
const [isTranscribing, setIsTranscribing] = useState(false)
const [audioStream, setAudioStream] = useState<MediaStream | null>(null)
const activeRecordingRef = useRef<any>(null)
const [isListening, setIsListening] = useState(false);
const [isSpeechSupported, setIsSpeechSupported] = useState(!!transcribeAudio);
const [isRecording, setIsRecording] = useState(false);
const [isTranscribing, setIsTranscribing] = useState(false);
const [audioStream, setAudioStream] = useState<MediaStream | null>(null);
const activeRecordingRef = useRef<any>(null);
useEffect(() => {
const checkSpeechSupport = async () => {
const hasMediaDevices = !!(
navigator.mediaDevices && navigator.mediaDevices.getUserMedia
)
setIsSpeechSupported(hasMediaDevices && !!transcribeAudio)
}
);
setIsSpeechSupported(hasMediaDevices && !!transcribeAudio);
};
checkSpeechSupport()
}, [transcribeAudio])
checkSpeechSupport();
}, [transcribeAudio]);
const stopRecording = async () => {
setIsRecording(false)
setIsTranscribing(true)
setIsRecording(false);
setIsTranscribing(true);
try {
// First stop the recording to get the final blob
recordAudio.stop()
recordAudio.stop();
// Wait for the recording promise to resolve with the final blob
const recording = await activeRecordingRef.current
const recording = await activeRecordingRef.current;
if (transcribeAudio) {
const text = await transcribeAudio(recording)
onTranscriptionComplete?.(text)
const text = await transcribeAudio(recording);
onTranscriptionComplete?.(text);
}
} catch (error) {
console.error("Error transcribing audio:", error)
console.error("Error transcribing audio:", error);
} finally {
setIsTranscribing(false)
setIsListening(false)
setIsTranscribing(false);
setIsListening(false);
if (audioStream) {
audioStream.getTracks().forEach((track) => track.stop())
setAudioStream(null)
audioStream.getTracks().forEach(track => track.stop());
setAudioStream(null);
}
activeRecordingRef.current = null
activeRecordingRef.current = null;
}
}
};
const toggleListening = async () => {
if (!isListening) {
try {
setIsListening(true)
setIsRecording(true)
setIsListening(true);
setIsRecording(true);
// Get audio stream first
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
})
setAudioStream(stream)
});
setAudioStream(stream);
// Start recording with the stream
activeRecordingRef.current = recordAudio(stream)
activeRecordingRef.current = recordAudio(stream);
} catch (error) {
console.error("Error recording audio:", error)
setIsListening(false)
setIsRecording(false)
console.error("Error recording audio:", error);
setIsListening(false);
setIsRecording(false);
if (audioStream) {
audioStream.getTracks().forEach((track) => track.stop())
setAudioStream(null)
audioStream.getTracks().forEach(track => track.stop());
setAudioStream(null);
}
}
} else {
await stopRecording()
await stopRecording();
}
}
};
return {
isListening,
@ -89,5 +89,5 @@ export function useAudioRecording({
audioStream,
toggleListening,
stopRecording,
}
};
}

View file

@ -1,67 +1,67 @@
import { useEffect, useRef, useState } from "react"
import { useEffect, useRef, useState } from "react";
// How many pixels from the bottom of the container to enable auto-scroll
const ACTIVATION_THRESHOLD = 50
const ACTIVATION_THRESHOLD = 50;
// Minimum pixels of scroll-up movement required to disable auto-scroll
const MIN_SCROLL_UP_THRESHOLD = 10
const MIN_SCROLL_UP_THRESHOLD = 10;
export function useAutoScroll(dependencies: React.DependencyList) {
const containerRef = useRef<HTMLDivElement | null>(null)
const previousScrollTop = useRef<number | null>(null)
const [shouldAutoScroll, setShouldAutoScroll] = useState(true)
const containerRef = useRef<HTMLDivElement | null>(null);
const previousScrollTop = useRef<number | null>(null);
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
const scrollToBottom = () => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}
};
const handleScroll = () => {
if (containerRef.current) {
const { scrollTop, scrollHeight, clientHeight } = containerRef.current
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
const distanceFromBottom = Math.abs(
scrollHeight - scrollTop - clientHeight
)
);
const isScrollingUp = previousScrollTop.current
? scrollTop < previousScrollTop.current
: false
: false;
const scrollUpDistance = previousScrollTop.current
? previousScrollTop.current - scrollTop
: 0
: 0;
const isDeliberateScrollUp =
isScrollingUp && scrollUpDistance > MIN_SCROLL_UP_THRESHOLD
isScrollingUp && scrollUpDistance > MIN_SCROLL_UP_THRESHOLD;
if (isDeliberateScrollUp) {
setShouldAutoScroll(false)
setShouldAutoScroll(false);
} else {
const isScrolledToBottom = distanceFromBottom < ACTIVATION_THRESHOLD
setShouldAutoScroll(isScrolledToBottom)
const isScrolledToBottom = distanceFromBottom < ACTIVATION_THRESHOLD;
setShouldAutoScroll(isScrolledToBottom);
}
previousScrollTop.current = scrollTop
previousScrollTop.current = scrollTop;
}
}
};
const handleTouchStart = () => {
setShouldAutoScroll(false)
}
setShouldAutoScroll(false);
};
useEffect(() => {
if (containerRef.current) {
previousScrollTop.current = containerRef.current.scrollTop
previousScrollTop.current = containerRef.current.scrollTop;
}
}, [])
}, []);
useEffect(() => {
if (shouldAutoScroll) {
scrollToBottom()
scrollToBottom();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies)
}, dependencies);
return {
containerRef,
@ -69,5 +69,5 @@ export function useAutoScroll(dependencies: React.DependencyList) {
handleScroll,
shouldAutoScroll,
handleTouchStart,
}
};
}

View file

@ -1,10 +1,10 @@
import { useLayoutEffect, useRef } from "react"
import { useLayoutEffect, useRef } from "react";
interface UseAutosizeTextAreaProps {
ref: React.RefObject<HTMLTextAreaElement | null>
maxHeight?: number
borderWidth?: number
dependencies: React.DependencyList
ref: React.RefObject<HTMLTextAreaElement | null>;
maxHeight?: number;
borderWidth?: number;
dependencies: React.DependencyList;
}
export function useAutosizeTextArea({
@ -13,27 +13,27 @@ export function useAutosizeTextArea({
borderWidth = 0,
dependencies,
}: UseAutosizeTextAreaProps) {
const originalHeight = useRef<number | null>(null)
const originalHeight = useRef<number | null>(null);
useLayoutEffect(() => {
if (!ref.current) return
if (!ref.current) return;
const currentRef = ref.current
const borderAdjustment = borderWidth * 2
const currentRef = ref.current;
const borderAdjustment = borderWidth * 2;
if (originalHeight.current === null) {
originalHeight.current = currentRef.scrollHeight - borderAdjustment
originalHeight.current = currentRef.scrollHeight - borderAdjustment;
}
currentRef.style.removeProperty("height")
const scrollHeight = currentRef.scrollHeight
currentRef.style.removeProperty("height");
const scrollHeight = currentRef.scrollHeight;
// Make sure we don't go over maxHeight
const clampedToMax = Math.min(scrollHeight, maxHeight)
const clampedToMax = Math.min(scrollHeight, maxHeight);
// Make sure we don't go less than the original height
const clampedToMin = Math.max(clampedToMax, originalHeight.current)
const clampedToMin = Math.max(clampedToMax, originalHeight.current);
currentRef.style.height = `${clampedToMin + borderAdjustment}px`
currentRef.style.height = `${clampedToMin + borderAdjustment}px`;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [maxHeight, ref, ...dependencies])
}, [maxHeight, ref, ...dependencies]);
}

View file

@ -1,36 +1,36 @@
import { useCallback, useRef, useState } from "react"
import { toast } from "sonner"
import { useCallback, useRef, useState } from "react";
import { toast } from "sonner";
type UseCopyToClipboardProps = {
text: string
copyMessage?: string
}
text: string;
copyMessage?: string;
};
export function useCopyToClipboard({
text,
copyMessage = "Copied to clipboard!",
}: UseCopyToClipboardProps) {
const [isCopied, setIsCopied] = useState(false)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
const [isCopied, setIsCopied] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const handleCopy = useCallback(() => {
navigator.clipboard
.writeText(text)
.then(() => {
toast.success(copyMessage)
setIsCopied(true)
toast.success(copyMessage);
setIsCopied(true);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
timeoutRef.current = null
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
timeoutRef.current = setTimeout(() => {
setIsCopied(false)
}, 2000)
setIsCopied(false);
}, 2000);
})
.catch(() => {
toast.error("Failed to copy to clipboard.")
})
}, [text, copyMessage])
toast.error("Failed to copy to clipboard.");
});
}, [text, copyMessage]);
return { isCopied, handleCopy }
return { isCopied, handleCopy };
}

View file

@ -20,7 +20,7 @@ interface UseInfiniteScrollOptions {
*/
export function useInfiniteScroll(
onLoadMore: (() => void) | undefined,
options: UseInfiniteScrollOptions = {},
options: UseInfiniteScrollOptions = {}
) {
const { enabled = true, threshold = 0.1, rootMargin = "100px" } = options;
const sentinelRef = useRef<HTMLTableRowElement>(null);
@ -29,7 +29,7 @@ export function useInfiniteScroll(
if (!onLoadMore || !enabled) return;
const observer = new IntersectionObserver(
(entries) => {
entries => {
const [entry] = entries;
if (entry.isIntersecting) {
onLoadMore();
@ -38,7 +38,7 @@ export function useInfiniteScroll(
{
threshold,
rootMargin,
},
}
);
const sentinel = sentinelRef.current;

View file

@ -4,7 +4,7 @@ const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
undefined
);
React.useEffect(() => {

View file

@ -38,7 +38,7 @@ interface UsePaginationParams<T> extends UsePaginationOptions {
limit: number;
model?: string;
order?: string;
},
}
) => Promise<PaginationResponse<T>>;
errorMessagePrefix: string;
enabled?: boolean;
@ -81,7 +81,7 @@ export function usePagination<T>({
const fetchLimit = targetRows || limit;
try {
setState((prev) => ({
setState(prev => ({
...prev,
status: isInitialLoad ? "loading" : "loading-more",
error: null,
@ -94,7 +94,7 @@ export function usePagination<T>({
...(order && { order }),
});
setState((prev) => ({
setState(prev => ({
...prev,
data: isInitialLoad
? response.data
@ -124,14 +124,14 @@ export function usePagination<T>({
? new Error(`${errorMessage} ${err.message}`)
: new Error(errorMessage);
setState((prev) => ({
setState(prev => ({
...prev,
error,
status: "error",
}));
}
},
[limit, model, order, fetchFunction, errorMessagePrefix, client, router],
[limit, model, order, fetchFunction, errorMessagePrefix, client, router]
);
/**