Skip to content

Commit 6f089ef

Browse files
fix: add auth redirects for /file-transcription page (#2183)
* fix: enable file upload for unauthenticated users in /file-transcription The UploadArea component was disabled when user was not signed in due to disabled={isProcessing || !user}. This prevented both drag-and-drop and click-to-upload from working. The handleFileSelect function already has a guard that shows an error message when the user is not signed in, so we can rely on that instead of disabling the entire upload area. Co-Authored-By: yujonglee <[email protected]> * refactor: redirect unauthenticated users to /auth on file upload, redirect authenticated users to /app/file-transcription - Add beforeLoad to redirect authenticated users to /app/file-transcription - Redirect unauthenticated users to /auth when they try to upload a file - Clean up unused code since this page is now only for unauthenticated users Co-Authored-By: yujonglee <[email protected]> * fix: pass search params in redirect to /app/file-transcription Forward the search params (including id) when redirecting authenticated users to /app/file-transcription to satisfy TypeScript's MakeRequiredSearchParams constraint. Co-Authored-By: yujonglee <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: yujonglee <[email protected]>
1 parent d871f18 commit 6f089ef

File tree

1 file changed

+35
-206
lines changed

1 file changed

+35
-206
lines changed

apps/web/src/routes/_view/file-transcription.tsx

Lines changed: 35 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
1-
import { createFileRoute, Link } from "@tanstack/react-router";
21
import {
3-
lazy,
4-
Suspense,
5-
useCallback,
6-
useEffect,
7-
useMemo,
8-
useRef,
9-
useState,
10-
} from "react";
2+
createFileRoute,
3+
Link,
4+
redirect,
5+
useNavigate,
6+
} from "@tanstack/react-router";
7+
import { lazy, Suspense, useMemo, useState } from "react";
118

129
import type { JSONContent } from "@hypr/tiptap/editor";
1310
import { EMPTY_TIPTAP_DOC } from "@hypr/tiptap/shared";
1411
import "@hypr/tiptap/styles.css";
1512

16-
import {
17-
FileInfo,
18-
TranscriptDisplay,
19-
} from "@/components/transcription/transcript-display";
13+
import { TranscriptDisplay } from "@/components/transcription/transcript-display";
2014
import { UploadArea } from "@/components/transcription/upload-area";
21-
import { getSupabaseBrowserClient } from "@/functions/supabase";
22-
import {
23-
getAudioPipelineStatus,
24-
startAudioPipeline,
25-
} from "@/functions/transcription";
26-
import { createUploadUrl } from "@/functions/upload";
15+
import { fetchUser } from "@/functions/auth";
2716

2817
const NoteEditor = lazy(() => import("@hypr/tiptap/editor"));
2918

@@ -32,165 +21,20 @@ export const Route = createFileRoute("/_view/file-transcription")({
3221
validateSearch: (search: Record<string, unknown>) => ({
3322
id: (search.id as string) || undefined,
3423
}),
24+
beforeLoad: async ({ search }) => {
25+
const user = await fetchUser();
26+
if (user) {
27+
throw redirect({ to: "/app/file-transcription", search });
28+
}
29+
},
3530
});
3631

37-
type ProcessingStatus =
38-
| "idle"
39-
| "uploading"
40-
| "queued"
41-
| "transcribing"
42-
| "done"
43-
| "error";
44-
4532
function Component() {
46-
const [user, setUser] = useState<{ email?: string; id?: string } | null>(
47-
null,
48-
);
49-
50-
useEffect(() => {
51-
let isMounted = true;
52-
async function loadUser() {
53-
try {
54-
const supabase = getSupabaseBrowserClient();
55-
const { data } = await supabase.auth.getUser();
56-
if (!isMounted) return;
57-
if (data.user?.email) {
58-
setUser({ email: data.user.email, id: data.user.id });
59-
} else {
60-
setUser(null);
61-
}
62-
} catch {
63-
if (isMounted) setUser(null);
64-
}
65-
}
66-
loadUser();
67-
return () => {
68-
isMounted = false;
69-
};
70-
}, []);
71-
72-
const [file, setFile] = useState<File | null>(null);
73-
const [status, setStatus] = useState<ProcessingStatus>("idle");
74-
const [error, setError] = useState<string | null>(null);
75-
const [transcript, setTranscript] = useState<string | null>(null);
33+
const navigate = useNavigate({ from: Route.fullPath });
7634
const [noteContent, setNoteContent] = useState<JSONContent>(EMPTY_TIPTAP_DOC);
7735

78-
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
79-
80-
const stopPolling = useCallback(() => {
81-
if (pollingRef.current) {
82-
clearInterval(pollingRef.current);
83-
pollingRef.current = null;
84-
}
85-
}, []);
86-
87-
useEffect(() => {
88-
return () => stopPolling();
89-
}, [stopPolling]);
90-
91-
const handleFileSelect = async (selectedFile: File) => {
92-
if (!user) {
93-
setError("Please sign in to transcribe audio files");
94-
return;
95-
}
96-
97-
setFile(selectedFile);
98-
setTranscript(null);
99-
setError(null);
100-
setStatus("uploading");
101-
102-
try {
103-
const uploadResult = await createUploadUrl({
104-
data: {
105-
fileName: selectedFile.name,
106-
fileType: selectedFile.type,
107-
},
108-
});
109-
110-
if ("error" in uploadResult && uploadResult.error) {
111-
throw new Error(uploadResult.message);
112-
}
113-
114-
if (!("signedUrl" in uploadResult)) {
115-
throw new Error("Failed to get upload URL");
116-
}
117-
118-
const uploadResponse = await fetch(uploadResult.signedUrl, {
119-
method: "PUT",
120-
headers: {
121-
"Content-Type": selectedFile.type,
122-
},
123-
body: selectedFile,
124-
});
125-
126-
if (!uploadResponse.ok) {
127-
throw new Error("Failed to upload file");
128-
}
129-
130-
setStatus("queued");
131-
132-
const pipelineResult = await startAudioPipeline({
133-
data: { fileId: uploadResult.fileId },
134-
});
135-
136-
if ("error" in pipelineResult && pipelineResult.error) {
137-
throw new Error(pipelineResult.message);
138-
}
139-
140-
if (!("pipelineId" in pipelineResult)) {
141-
throw new Error("Failed to start transcription");
142-
}
143-
144-
const { pipelineId } = pipelineResult;
145-
146-
pollingRef.current = setInterval(async () => {
147-
try {
148-
const statusResult = await getAudioPipelineStatus({
149-
data: { pipelineId },
150-
});
151-
152-
if ("error" in statusResult && statusResult.error) {
153-
throw new Error(statusResult.message);
154-
}
155-
156-
if (!("status" in statusResult)) {
157-
return;
158-
}
159-
160-
const { status: pipelineStatus } = statusResult;
161-
162-
if (pipelineStatus.status === "TRANSCRIBING") {
163-
setStatus("transcribing");
164-
} else if (pipelineStatus.status === "DONE") {
165-
setStatus("done");
166-
setTranscript(pipelineStatus.transcript ?? null);
167-
stopPolling();
168-
} else if (pipelineStatus.status === "ERROR") {
169-
setStatus("error");
170-
setError(pipelineStatus.error ?? "Transcription failed");
171-
stopPolling();
172-
}
173-
} catch (err) {
174-
setStatus("error");
175-
setError(
176-
err instanceof Error ? err.message : "Failed to check status",
177-
);
178-
stopPolling();
179-
}
180-
}, 2000);
181-
} catch (err) {
182-
setStatus("error");
183-
setError(err instanceof Error ? err.message : "An error occurred");
184-
}
185-
};
186-
187-
const handleRemoveFile = () => {
188-
stopPolling();
189-
setFile(null);
190-
setTranscript(null);
191-
setStatus("idle");
192-
setError(null);
193-
setNoteContent(EMPTY_TIPTAP_DOC);
36+
const handleFileSelect = () => {
37+
navigate({ to: "/auth" });
19438
};
19539

19640
const mentionConfig = useMemo(
@@ -203,9 +47,6 @@ function Component() {
20347
[],
20448
);
20549

206-
const isProcessing =
207-
status === "uploading" || status === "queued" || status === "transcribing";
208-
20950
return (
21051
<div className="min-h-[calc(100vh-200px)]">
21152
<div className="max-w-7xl mx-auto border-x border-neutral-100">
@@ -244,18 +85,10 @@ function Component() {
24485
</div>
24586

24687
<div className="p-6 space-y-6">
247-
{!file ? (
248-
<UploadArea
249-
onFileSelect={handleFileSelect}
250-
disabled={isProcessing || !user}
251-
/>
252-
) : (
253-
<FileInfo
254-
fileName={file.name}
255-
fileSize={file.size}
256-
onRemove={handleRemoveFile}
257-
/>
258-
)}
88+
<UploadArea
89+
onFileSelect={handleFileSelect}
90+
disabled={false}
91+
/>
25992

26093
<div>
26194
<h3 className="text-sm font-medium text-neutral-700 mb-3">
@@ -285,14 +118,12 @@ function Component() {
285118
Combined notes with transcript
286119
</p>
287120
</div>
288-
{transcript && !user && (
289-
<Link
290-
to="/auth"
291-
className="px-4 h-8 flex items-center text-sm bg-linear-to-t from-stone-600 to-stone-500 text-white rounded-full shadow-md hover:shadow-lg hover:scale-[102%] active:scale-[98%] transition-all"
292-
>
293-
Sign in
294-
</Link>
295-
)}
121+
<Link
122+
to="/auth"
123+
className="px-4 h-8 flex items-center text-sm bg-linear-to-t from-stone-600 to-stone-500 text-white rounded-full shadow-md hover:shadow-lg hover:scale-[102%] active:scale-[98%] transition-all"
124+
>
125+
Sign in
126+
</Link>
296127
</div>
297128

298129
<div className="border border-neutral-200 rounded-lg shadow-sm bg-white overflow-hidden">
@@ -305,20 +136,18 @@ function Component() {
305136

306137
<div className="p-6">
307138
<TranscriptDisplay
308-
transcript={user ? transcript : null}
309-
status={user ? status : "idle"}
310-
error={error}
139+
transcript={null}
140+
status="idle"
141+
error={null}
311142
/>
312143
</div>
313144
</div>
314145

315-
{transcript && !user && (
316-
<div className="p-4 bg-stone-50 border border-neutral-200 rounded-sm">
317-
<p className="text-sm text-neutral-600">
318-
Sign in to view and save your transcription results
319-
</p>
320-
</div>
321-
)}
146+
<div className="p-4 bg-stone-50 border border-neutral-200 rounded-sm">
147+
<p className="text-sm text-neutral-600">
148+
Sign in to view and save your transcription results
149+
</p>
150+
</div>
322151
</div>
323152
</div>
324153
</div>

0 commit comments

Comments
 (0)