diff --git a/auth-view/src/pages/LoginPage.tsx b/auth-view/src/pages/LoginPage.tsx index e36ee75..dfd9ee6 100644 --- a/auth-view/src/pages/LoginPage.tsx +++ b/auth-view/src/pages/LoginPage.tsx @@ -1,6 +1,5 @@ -import { useEffect, useState} from "react"; +import { useEffect, useState, type SubmitEvent } from "react"; import { useSearchParams, Link } from "react-router-dom"; -import * as React from "react"; function saveTokensAndRedirect(token: string, refreshToken: string, redirect: string) { localStorage.setItem("token", token); @@ -26,7 +25,7 @@ export default function LoginPage() { } }, [searchParams, redirect]); - async function handleSubmit(e: React.SyntheticEvent) { + async function handleSubmit(e: SubmitEvent) { e.preventDefault(); setError(""); setLoading(true); diff --git a/auth-view/src/pages/RegisterPage.tsx b/auth-view/src/pages/RegisterPage.tsx index ba6c4a5..d9c752c 100644 --- a/auth-view/src/pages/RegisterPage.tsx +++ b/auth-view/src/pages/RegisterPage.tsx @@ -1,4 +1,4 @@ -import { useState, FormEvent } from "react"; +import { useState, type SubmitEvent } from "react"; import { useSearchParams, Link } from "react-router-dom"; function saveTokensAndRedirect(token: string, refreshToken: string, redirect: string) { @@ -18,7 +18,7 @@ export default function RegisterPage() { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); - async function handleSubmit(e: FormEvent) { + async function handleSubmit(e: SubmitEvent) { e.preventDefault(); setError(""); @@ -32,7 +32,7 @@ export default function RegisterPage() { const res = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, email, password }), + body: JSON.stringify({ username, email, password, confirmPassword }), }); const data = await res.json(); if (data.success && data.payload?.token) { diff --git a/rag-view/src/features/uploadFilesSliceOld.js b/rag-view/src/features/uploadFilesSliceOld.js deleted file mode 100644 index 8d305a0..0000000 --- a/rag-view/src/features/uploadFilesSliceOld.js +++ /dev/null @@ -1,333 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -//import { addLoadedFiles } from "./slices/details-slice"; -import fetchUserProfile from "./fetch-async/fetchUserProfile"; -import { - BASE_URL, - METHOD_POST_QUERY, - PREFIX_DOCUMENT, - PREFIX_UPLOAD_STREAM, - FORM_DATA_FILES, - DEFAULT_ERROR_STATUS, - PROGRESS_STATUS_COMPLETED, - PROGRESS_STATUS_ERROR, - ERROR_NO_RESPONSE, - UNKNOWN_ERROR, - MAX_FILES_TO_UPLOAD, -} from "./constants"; - -const initialState = { - uploading: false, - error: { - status: null, - message: "", - }, - success: false, - areFilesValid: true, - quotaError: null, - uploadedFiles: [], - progress: { - percent: 0, - processedFiles: 0, - totalFiles: 0, - currentFile: "", - status: "", - }, -}; - -// Store AbortController outside Redux state -let currentAbortController = null; - -const uploadFilesSlice = createSlice({ - name: "uploadFiles", - initialState, - reducers: { - resetUploadState() { - // Abort any ongoing upload when resetting - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - return initialState; - }, - setAreFilesValid(state, action) { - state.areFilesValid = action.payload; - }, - setQuotaError(state, action) { - state.quotaError = action.payload; - }, - clearQuotaError(state) { - state.quotaError = null; - }, - uploadStarted(state, action) { - state.uploading = true; - state.error = { status: null, message: "" }; - state.success = false; - state.quotaError = null; - state.progress = { - percent: 0, - processedFiles: 0, - totalFiles: action.payload.totalFiles, - currentFile: "", - status: "starting", - }; - }, - progressUpdated(state, action) { - state.progress = action.payload; - }, - uploadCompleted(state, action) { - state.uploading = false; - state.success = true; - state.uploadedFiles = action.payload.fileNames; - }, - uploadFailed(state, action) { - state.uploading = false; - state.success = false; - state.error = { - status: action.payload.status ?? DEFAULT_ERROR_STATUS, - message: action.payload.message ?? UNKNOWN_ERROR, - }; - }, - uploadCancelled(state) { - state.uploading = false; - state.success = false; - state.error = { status: null, message: "" }; - state.progress = initialState.progress; - }, - }, -}); - -export const { - resetUploadState, - setAreFilesValid, - setQuotaError, - clearQuotaError, - uploadStarted, - progressUpdated, - uploadCompleted, - uploadFailed, - uploadCancelled, -} = uploadFilesSlice.actions; - -export const cancelUpload = () => (dispatch) => { - if (currentAbortController) { - currentAbortController.abort(); - currentAbortController = null; - } - dispatch(uploadCancelled()); -}; - -/** - * Validate files against quota limits - * Returns { valid: boolean, error: string | null } - */ -export const validateFilesQuota = (files) => (dispatch, getState) => { - const state = getState(); - const loadedFiles = state.userDetails?.loadedFiles || []; - const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; - - const loadedCount = loadedFiles.length; - const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); - const filesCount = files.length; - - // Check max files per upload - if (filesCount > MAX_FILES_TO_UPLOAD) { - const errorMsg = `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`; - dispatch(setQuotaError(errorMsg)); - dispatch(setAreFilesValid(false)); - return { valid: false, error: errorMsg }; - } - - // Check remaining slots - if (filesCount > remainingSlots) { - const errorMsg = - remainingSlots === 0 - ? `You have reached the maximum number of files (${maxFilesToLoad})` - : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`; - dispatch(setQuotaError(errorMsg)); - dispatch(setAreFilesValid(false)); - return { valid: false, error: errorMsg }; - } - - dispatch(clearQuotaError()); - dispatch(setAreFilesValid(true)); - return { valid: true, error: null }; -}; - -/** - * Check if user can upload files (has remaining slots) - */ -export const canUploadFiles = () => (dispatch, getState) => { - const state = getState(); - const loadedFiles = state.userDetails?.loadedFiles || []; - const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; - - const remainingSlots = Math.max(0, maxFilesToLoad - loadedFiles.length); - return remainingSlots > 0; -}; - -/** - * Get remaining upload slots - */ -export const getRemainingSlots = () => (dispatch, getState) => { - const state = getState(); - const loadedFiles = state.userDetails?.loadedFiles || []; - const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; - - return Math.max(0, maxFilesToLoad - loadedFiles.length); -}; - -/** - * Thunk for uploading files with SSE progress streaming. - */ -export const uploadFilesWithProgress = - (files) => async (dispatch, getState) => { - const filesArray = Array.from(files); - const fileNames = filesArray.map((f) => f.name); - - // Validate quota before upload - const state = getState(); - const loadedFiles = state.userDetails?.loadedFiles || []; - const maxFilesToLoad = state.userDetails?.maxFilesToLoad || 0; - const loadedCount = loadedFiles.length; - const remainingSlots = Math.max(0, maxFilesToLoad - loadedCount); - - // Check max files per upload - if (filesArray.length > MAX_FILES_TO_UPLOAD) { - dispatch( - uploadFailed({ - status: 400, - message: `You can upload maximum ${MAX_FILES_TO_UPLOAD} files at once`, - }) - ); - return; - } - - // Check remaining slots - if (filesArray.length > remainingSlots) { - dispatch( - uploadFailed({ - status: 400, - message: - remainingSlots === 0 - ? `You have reached the maximum number of files (${maxFilesToLoad})` - : `You can only upload ${remainingSlots} more file(s). Already loaded: ${loadedCount}/${maxFilesToLoad}`, - }) - ); - return; - } - - // Create new AbortController for this upload - currentAbortController = new AbortController(); - const signal = currentAbortController.signal; - - dispatch(uploadStarted({ totalFiles: filesArray.length })); - - const formData = new FormData(); - filesArray.forEach((file) => { - formData.append(FORM_DATA_FILES, file); - }); - - const token = state.userDetails?.token; - - const headers = {}; - if (token) { - headers.Authorization = `Bearer ${token}`; - } - - try { - const response = await fetch( - `${BASE_URL}${PREFIX_DOCUMENT}${PREFIX_UPLOAD_STREAM}`, - { - method: METHOD_POST_QUERY, - headers, - body: formData, - signal, - } - ); - - if (!response.ok) { - dispatch( - uploadFailed({ - status: response.status, - message: `Server error: ${response.statusText}`, - }) - ); - return; - } - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let buffer = ""; - - while (true) { - // Check if aborted before reading - if (signal.aborted) { - reader.cancel(); - return; - } - - const { done, value } = await reader.read(); - - if (done) { - break; - } - - buffer += decoder.decode(value, { stream: true }); - - const lines = buffer.split("\n"); - buffer = lines.pop() || ""; - - for (const line of lines) { - if (line.startsWith("data:")) { - const jsonStr = line.slice(5).trim(); - if (jsonStr && jsonStr.startsWith("{")) { - try { - const progressData = JSON.parse(jsonStr); - dispatch(progressUpdated(progressData)); - - if (progressData.status === PROGRESS_STATUS_COMPLETED) { - dispatch(uploadCompleted({ fileNames })); - dispatch(fetchUserProfile()); - //dispatch(addLoadedFiles(fileNames)); - } else if (progressData.status === PROGRESS_STATUS_ERROR) { - dispatch( - uploadFailed({ - status: DEFAULT_ERROR_STATUS, - message: `Error processing file: ${progressData.currentFile}`, - }) - ); - } - } catch (parseError) { - console.error("Failed to parse SSE data:", parseError); - } - } - } - } - } - - // Ensure completion if stream ended without explicit completed status - const currentState = getState(); - if (currentState.uploadFiles.uploading) { - dispatch(uploadCompleted({ fileNames })); - dispatch(addLoadedFiles(fileNames)); - } - } catch (err) { - // Don't dispatch error if it was an intentional abort - if (err.name === "AbortError") { - console.log("Upload cancelled by user"); - return; - } - - console.error("Upload stream error:", err); - dispatch( - uploadFailed({ - status: DEFAULT_ERROR_STATUS, - message: `${ERROR_NO_RESPONSE}: ${err.message}`, - }) - ); - } finally { - currentAbortController = null; - } - }; - -//export default uploadFilesSlice.reducer; diff --git a/rag-view/src/features/userDetailsSliceOld.js b/rag-view/src/features/userDetailsSliceOld.js deleted file mode 100644 index 555d36f..0000000 --- a/rag-view/src/features/userDetailsSliceOld.js +++ /dev/null @@ -1,199 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import fetchLoginUser from "./fetch-async/fetchLoginUser"; -import fetchRegisterUser from "./fetch-async/fetchRegisterUser"; -import fetchRefreshToken from "./fetch-async/fetchRefreshToken"; -import fetchUserProfile from "./fetch-async/fetchUserProfile"; -import fetchDeleteUploaded from "./fetch-async/fetchDeleteUploaded"; - -import { - USER_NAME_UNDEFINED, - USER_EMAIL_UNDEFINED, - JWT_TOKEN, - JWT_REFRESH_TOKEN, - TYPE_WINDOW_UNDEFINED, - TOKEN_UNDEFINED, - UNKNOWN_ERROR, -} from "./constants"; - -const getInitialToken = () => { - if (typeof window === TYPE_WINDOW_UNDEFINED) return null; - - const token = localStorage.getItem(JWT_TOKEN); - if (!token || token === TOKEN_UNDEFINED) return null; - return token; -}; - -const initialState = { - userId: null, - userName: USER_NAME_UNDEFINED, - userEmail: USER_EMAIL_UNDEFINED, - loadedFiles: [], - maxFilesToLoad: 0, - token: getInitialToken(), - refreshToken: localStorage.getItem(JWT_REFRESH_TOKEN), - loading: false, - deleting: false, - deleteSuccess: false, - deletedCount: 0, - error: null, -}; - -const userDetailsSlice = createSlice({ - name: "userDetails", - initialState, - reducers: { - logoutUser(state) { - state.userId = null; - state.userName = USER_NAME_UNDEFINED; - state.userEmail = USER_EMAIL_UNDEFINED; - state.loadedFiles = []; - state.maxFilesToLoad = 0; - state.token = null; - state.refreshToken = null; - state.loading = false; - state.deleting = false; - state.deleteSuccess = false; - state.deletedCount = 0; - state.error = null; - localStorage.removeItem(JWT_TOKEN); - localStorage.removeItem(JWT_REFRESH_TOKEN); - }, - clearError(state) { - state.error = null; - }, - clearDeleteStatus(state) { - state.deleteSuccess = false; - state.deletedCount = 0; - }, - addLoadedFiles(state, action) { - state.loadedFiles = [...state.loadedFiles, ...action.payload]; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetchRegisterUser.pending, (state) => { - state.loading = true; - state.error = null; - }); - builder.addCase(fetchRegisterUser.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - state.userName = action.payload.payload.username; - state.userEmail = action.payload.payload.email; - const token = action.payload.payload.token; - state.token = token || null; - if (token) { - localStorage.setItem(JWT_TOKEN, token); - } else { - localStorage.removeItem(JWT_TOKEN); - } - const refreshToken = action.payload.payload.refreshToken; - state.refreshToken = refreshToken || null; - if (refreshToken) { - localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); - } else { - localStorage.removeItem(JWT_REFRESH_TOKEN); - } - }); - builder.addCase(fetchRegisterUser.rejected, (state, action) => { - state.loading = false; - state.error = action.payload?.status ?? 500; - }); - - builder.addCase(fetchLoginUser.pending, (state) => { - state.loading = true; - state.error = null; - }); - builder.addCase(fetchLoginUser.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - state.userName = action.payload.payload.username; - state.userEmail = action.payload.payload.email; - - const token = action.payload.payload.token; - state.token = token || null; - if (token) { - localStorage.setItem(JWT_TOKEN, token); - } else { - localStorage.removeItem(JWT_TOKEN); - } - - const refreshToken = action.payload.payload.refreshToken; - state.refreshToken = refreshToken || null; - if (refreshToken) { - localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); - } else { - localStorage.removeItem(JWT_REFRESH_TOKEN); - } - }); - builder.addCase(fetchLoginUser.rejected, (state, action) => { - state.loading = false; - state.error = action.payload ?? { status: 500, message: "Unknown error" }; - }); - - builder.addCase(fetchRefreshToken.fulfilled, (state, action) => { - const token = action.payload?.payload?.token; - const refreshToken = action.payload?.payload?.refreshToken; - - state.token = token || null; - state.refreshToken = refreshToken || null; - - if (token) localStorage.setItem(JWT_TOKEN, token); - else localStorage.removeItem(JWT_TOKEN); - - if (refreshToken) localStorage.setItem(JWT_REFRESH_TOKEN, refreshToken); - else localStorage.removeItem(JWT_REFRESH_TOKEN); - }); - - builder.addCase(fetchRefreshToken.rejected, (state, action) => { - state.error = action.payload ?? { status: 500, message: UNKNOWN_ERROR }; - }); - - builder.addCase(fetchUserProfile.pending, (state) => { - state.loading = true; - state.error = null; - }); - - builder.addCase(fetchUserProfile.fulfilled, (state, action) => { - state.loading = false; - state.error = null; - - const p = action.payload?.payload; - - if (p?.id) state.userId = p.id; - if (p?.username) state.userName = p.username; - if (p?.email) state.userEmail = p.email; - if (p?.loadedFiles) state.loadedFiles = p.loadedFiles; - if (p?.maxLoadedFiles != null) state.maxFilesToLoad = p.maxLoadedFiles; - }); - - builder.addCase(fetchUserProfile.rejected, (state, action) => { - state.loading = false; - state.error = action.payload ?? { status: 500, message: "Unknown error" }; - }); - - // Delete uploaded files - builder.addCase(fetchDeleteUploaded.pending, (state) => { - state.deleting = true; - state.deleteSuccess = false; - state.deletedCount = 0; - state.error = null; - }); - - builder.addCase(fetchDeleteUploaded.fulfilled, (state, action) => { - state.deleting = false; - state.deleteSuccess = true; - state.deletedCount = action.payload?.payload ?? 0; - state.loadedFiles = []; - state.error = null; - }); - - builder.addCase(fetchDeleteUploaded.rejected, (state, action) => { - state.deleting = false; - state.deleteSuccess = false; - state.error = action.payload ?? { status: 500, message: "Unknown error" }; - }); - }, -}); - -//export const { logoutUser, clearError, clearDeleteStatus, addLoadedFiles } = userDetailsSlice.actions; -//export default userDetailsSlice.reducer;