mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
feat: migrate components to React Query hooks
- Add comprehensive API hooks for all operations - Migrate rooms page to use React Query mutations - Update transcript title component to use mutation hook - Refactor share/privacy component with proper error handling - Remove direct API client usage in favor of hooks
This commit is contained in:
@@ -15,9 +15,15 @@ import {
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import useApi from "../../lib/useApi";
|
||||
import useRoomList from "./useRoomList";
|
||||
import { ApiError, Room } from "../../lib/api-types";
|
||||
import {
|
||||
useRoomCreate,
|
||||
useRoomUpdate,
|
||||
useRoomDelete,
|
||||
useZulipStreams,
|
||||
useZulipTopics,
|
||||
} from "../../lib/api-hooks";
|
||||
import { RoomList } from "./_components/RoomList";
|
||||
import { PaginationPage } from "../browse/_components/Pagination";
|
||||
|
||||
@@ -75,64 +81,42 @@ export default function RoomsList() {
|
||||
const [room, setRoom] = useState(roomInitialState);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editRoomId, setEditRoomId] = useState("");
|
||||
const api = useApi();
|
||||
// TODO seems to be no setPage calls
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const { loading, response, refetch } = useRoomList(PaginationPage(page));
|
||||
const [streams, setStreams] = useState<Stream[]>([]);
|
||||
const [topics, setTopics] = useState<Topic[]>([]);
|
||||
const [nameError, setNameError] = useState("");
|
||||
const [linkCopied, setLinkCopied] = useState("");
|
||||
interface Stream {
|
||||
stream_id: number;
|
||||
name: string;
|
||||
}
|
||||
const [selectedStreamId, setSelectedStreamId] = useState<number | null>(null);
|
||||
|
||||
// React Query hooks
|
||||
const createRoomMutation = useRoomCreate();
|
||||
const updateRoomMutation = useRoomUpdate();
|
||||
const deleteRoomMutation = useRoomDelete();
|
||||
const { data: streams = [] } = useZulipStreams();
|
||||
const { data: topics = [] } = useZulipTopics(selectedStreamId);
|
||||
interface Topic {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Update selected stream ID when zulip stream changes
|
||||
useEffect(() => {
|
||||
const fetchZulipStreams = async () => {
|
||||
if (!api) return;
|
||||
|
||||
try {
|
||||
const response = await api.v1ZulipGetStreams();
|
||||
setStreams(response);
|
||||
} catch (error) {
|
||||
console.error("Error fetching Zulip streams:", error);
|
||||
if (room.zulipStream && streams.length > 0) {
|
||||
const selectedStream = streams.find(
|
||||
(s: any) => s.name === room.zulipStream,
|
||||
);
|
||||
if (selectedStream) {
|
||||
setSelectedStreamId((selectedStream as any).stream_id);
|
||||
}
|
||||
};
|
||||
|
||||
if (room.zulipAutoPost) {
|
||||
fetchZulipStreams();
|
||||
} else {
|
||||
setSelectedStreamId(null);
|
||||
}
|
||||
}, [room.zulipAutoPost, !api]);
|
||||
}, [room.zulipStream, streams]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchZulipTopics = async () => {
|
||||
if (!api || !room.zulipStream) return;
|
||||
try {
|
||||
const selectedStream = streams.find((s) => s.name === room.zulipStream);
|
||||
if (selectedStream) {
|
||||
const response = await api.v1ZulipGetTopics({
|
||||
streamId: selectedStream.stream_id,
|
||||
});
|
||||
setTopics(response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching Zulip topics:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchZulipTopics();
|
||||
}, [room.zulipStream, streams, api]);
|
||||
|
||||
const streamOptions: SelectOption[] = streams.map((stream) => {
|
||||
const streamOptions: SelectOption[] = streams.map((stream: any) => {
|
||||
return { label: stream.name, value: stream.name };
|
||||
});
|
||||
|
||||
const topicOptions: SelectOption[] = topics.map((topic) => ({
|
||||
const topicOptions: SelectOption[] = topics.map((topic: any) => ({
|
||||
label: topic.name,
|
||||
value: topic.name,
|
||||
}));
|
||||
@@ -175,13 +159,15 @@ export default function RoomsList() {
|
||||
};
|
||||
|
||||
if (isEditing) {
|
||||
await api?.v1RoomsUpdate({
|
||||
roomId: editRoomId,
|
||||
requestBody: roomData,
|
||||
await updateRoomMutation.mutateAsync({
|
||||
params: {
|
||||
path: { room_id: editRoomId },
|
||||
},
|
||||
body: roomData,
|
||||
});
|
||||
} else {
|
||||
await api?.v1RoomsCreate({
|
||||
requestBody: roomData,
|
||||
await createRoomMutation.mutateAsync({
|
||||
body: roomData,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -226,8 +212,10 @@ export default function RoomsList() {
|
||||
|
||||
const handleDeleteRoom = async (roomId: string) => {
|
||||
try {
|
||||
await api?.v1RoomsDelete({
|
||||
roomId,
|
||||
await deleteRoomMutation.mutateAsync({
|
||||
params: {
|
||||
path: { room_id: roomId },
|
||||
},
|
||||
});
|
||||
refetch();
|
||||
} catch (err) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
createListCollection,
|
||||
} from "@chakra-ui/react";
|
||||
import { LuShare2 } from "react-icons/lu";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { useTranscriptUpdate } from "../../lib/api-hooks";
|
||||
import useSessionUser from "../../lib/useSessionUser";
|
||||
import { CustomSession } from "../../lib/types";
|
||||
import ShareLink from "./shareLink";
|
||||
@@ -54,12 +54,9 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
||||
);
|
||||
const [shareLoading, setShareLoading] = useState(false);
|
||||
const requireLogin = featureEnabled("requireLogin");
|
||||
const api = useApi();
|
||||
const updateTranscriptMutation = useTranscriptUpdate();
|
||||
|
||||
const updateShareMode = async (selectedValue: string) => {
|
||||
if (!api)
|
||||
throw new Error("ShareLink's API should always be ready at this point");
|
||||
|
||||
const selectedOption = shareOptionsData.find(
|
||||
(option) => option.value === selectedValue,
|
||||
);
|
||||
@@ -71,16 +68,23 @@ export default function ShareAndPrivacy(props: ShareAndPrivacyProps) {
|
||||
share_mode: selectedValue as "public" | "semi-private" | "private",
|
||||
};
|
||||
|
||||
const updatedTranscript = await api.v1TranscriptUpdate({
|
||||
transcriptId: props.transcriptResponse.id,
|
||||
requestBody,
|
||||
});
|
||||
setShareMode(
|
||||
shareOptionsData.find(
|
||||
(option) => option.value === updatedTranscript.share_mode,
|
||||
) || shareOptionsData[0],
|
||||
);
|
||||
setShareLoading(false);
|
||||
try {
|
||||
const updatedTranscript = await updateTranscriptMutation.mutateAsync({
|
||||
params: {
|
||||
path: { transcript_id: props.transcriptResponse.id },
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
setShareMode(
|
||||
shareOptionsData.find(
|
||||
(option) => option.value === (updatedTranscript as any).share_mode,
|
||||
) || shareOptionsData[0],
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to update share mode:", err);
|
||||
} finally {
|
||||
setShareLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const userId = useSessionUser().id;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { UpdateTranscript } from "../../lib/api-types";
|
||||
import useApi from "../../lib/useApi";
|
||||
import { useTranscriptUpdate } from "../../lib/api-hooks";
|
||||
import { Heading, IconButton, Input, Flex, Spacer } from "@chakra-ui/react";
|
||||
import { LuPen } from "react-icons/lu";
|
||||
|
||||
@@ -14,24 +14,27 @@ const TranscriptTitle = (props: TranscriptTitle) => {
|
||||
const [displayedTitle, setDisplayedTitle] = useState(props.title);
|
||||
const [preEditTitle, setPreEditTitle] = useState(props.title);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const api = useApi();
|
||||
const updateTranscriptMutation = useTranscriptUpdate();
|
||||
|
||||
const updateTitle = async (newTitle: string, transcriptId: string) => {
|
||||
if (!api) return;
|
||||
try {
|
||||
const requestBody: UpdateTranscript = {
|
||||
title: newTitle,
|
||||
};
|
||||
const updatedTranscript = await api?.v1TranscriptUpdate({
|
||||
transcriptId,
|
||||
requestBody,
|
||||
await updateTranscriptMutation.mutateAsync({
|
||||
params: {
|
||||
path: { transcript_id: transcriptId },
|
||||
},
|
||||
body: requestBody,
|
||||
});
|
||||
if (props.onUpdate) {
|
||||
props.onUpdate(newTitle);
|
||||
}
|
||||
console.log("Updated transcript:", updatedTranscript);
|
||||
console.log("Updated transcript title:", newTitle);
|
||||
} catch (err) {
|
||||
console.error("Failed to update transcript:", err);
|
||||
// Revert title on error
|
||||
setDisplayedTitle(preEditTitle);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -193,3 +193,322 @@ export function useZulipTopics(streamId: number | null) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Transcript mutations
|
||||
export function useTranscriptUpdate() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation("patch", "/v1/transcripts/{transcript_id}", {
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate and refetch transcript data
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions("get", "/v1/transcripts/{transcript_id}", {
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
}).queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error updating the transcript");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptPostToZulip() {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useMutation("post", "/v1/transcripts/{transcript_id}/zulip", {
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error posting to Zulip");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useTranscriptUploadAudio() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation(
|
||||
"post",
|
||||
"/v1/transcripts/{transcript_id}/record/upload",
|
||||
{
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate transcript to refresh status
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error uploading the audio file");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Transcript queries
|
||||
export function useTranscriptWaveform(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/audio/waveform",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: transcriptId || "" },
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error fetching the waveform");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptMP3(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/audio/mp3",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: transcriptId || "" },
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error fetching the MP3");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptTopics(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/topics",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: transcriptId || "" },
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error fetching topics");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptTopicsWithWords(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/topics/with-words",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: transcriptId || "" },
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(
|
||||
error as Error,
|
||||
"There was an error fetching topics with words",
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Participant operations
|
||||
export function useTranscriptParticipants(transcriptId: string | null) {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useQuery(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/participants",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: transcriptId || "" },
|
||||
},
|
||||
},
|
||||
{
|
||||
enabled: !!transcriptId,
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error fetching participants");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptParticipantUpdate() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation(
|
||||
"patch",
|
||||
"/v1/transcripts/{transcript_id}/participants/{participant_id}",
|
||||
{
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate participants list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/participants",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error updating the participant");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptSpeakerAssign() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation(
|
||||
"post",
|
||||
"/v1/transcripts/{transcript_id}/speaker/assign",
|
||||
{
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate transcript and participants
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/participants",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error assigning the speaker");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function useTranscriptSpeakerMerge() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation(
|
||||
"post",
|
||||
"/v1/transcripts/{transcript_id}/speaker/merge",
|
||||
{
|
||||
onSuccess: (data, variables) => {
|
||||
// Invalidate transcript and participants
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions(
|
||||
"get",
|
||||
"/v1/transcripts/{transcript_id}/participants",
|
||||
{
|
||||
params: {
|
||||
path: { transcript_id: variables.params.path.transcript_id },
|
||||
},
|
||||
},
|
||||
).queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error merging speakers");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Meeting operations
|
||||
export function useMeetingAudioConsent() {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useMutation("post", "/v1/meetings/{meeting_id}/consent", {
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error recording consent");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// WebRTC operations
|
||||
export function useTranscriptWebRTC() {
|
||||
const { setError } = useError();
|
||||
|
||||
return $api.useMutation(
|
||||
"post",
|
||||
"/v1/transcripts/{transcript_id}/record/webrtc",
|
||||
{
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error with WebRTC connection");
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Transcript creation
|
||||
export function useTranscriptCreate() {
|
||||
const { setError } = useError();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return $api.useMutation("post", "/v1/transcripts", {
|
||||
onSuccess: () => {
|
||||
// Invalidate transcripts list
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: $api.queryOptions("get", "/v1/transcripts/search").queryKey,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
setError(error as Error, "There was an error creating the transcript");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user