mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 01:21:48 +03:00
feat: update header and improve ux (#1164)
* feat: add Knowledge tab * feat: use tanstack query for knowledges fetching * feat: update header * feat: remove upload page * feat: make chat page the default redirection page * feat(homePage): redirect user to chat page when already authenticated
This commit is contained in:
parent
70ffa5d6be
commit
2b4c3ecbbc
@ -31,7 +31,7 @@ describe("Login component", () => {
|
||||
it("redirects to /upload if user is already signed in and is not coming from another page", () => {
|
||||
render(<Login />);
|
||||
expect(mockRedirect).toHaveBeenCalledTimes(1);
|
||||
expect(mockRedirect).toHaveBeenCalledWith("/upload");
|
||||
expect(mockRedirect).toHaveBeenCalledWith("/chat");
|
||||
});
|
||||
|
||||
it('redirects to "/previous-page" if user is already signed in and previous page is set', () => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToPreviousPageOrChatPage } from "@/lib/helpers/redirectToPreviousPageOrChatPage";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
@ -25,28 +25,28 @@ export const useLogin = () => {
|
||||
password: password,
|
||||
});
|
||||
if (error) {
|
||||
console.log(error.message)
|
||||
console.log(error.message);
|
||||
if (error.message.includes("Failed")) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("Failedtofetch",{ ns: 'login' })
|
||||
text: t("Failedtofetch", { ns: "login" }),
|
||||
});
|
||||
} else if (error.message.includes("Invalid")) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("Invalidlogincredentials",{ ns: 'login' })
|
||||
text: t("Invalidlogincredentials", { ns: "login" }),
|
||||
});
|
||||
} else {
|
||||
publish({
|
||||
variant: "danger",
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
|
||||
text: error.message
|
||||
text: error.message,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
publish({
|
||||
variant: "success",
|
||||
text: t("loginSuccess",{ ns: 'login' })
|
||||
text: t("loginSuccess", { ns: "login" }),
|
||||
});
|
||||
}
|
||||
setIsPending(false);
|
||||
@ -55,14 +55,7 @@ export const useLogin = () => {
|
||||
useEffect(() => {
|
||||
if (session?.user !== undefined) {
|
||||
void track("SIGNED_IN");
|
||||
|
||||
const previousPage = sessionStorage.getItem("previous-page");
|
||||
if (previousPage === null) {
|
||||
redirect("/upload");
|
||||
} else {
|
||||
sessionStorage.removeItem("previous-page");
|
||||
redirect(previousPage);
|
||||
}
|
||||
redirectToPreviousPageOrChatPage();
|
||||
}
|
||||
}, [session?.user]);
|
||||
|
||||
|
@ -1,10 +1,24 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { getProcessEnvManager } from "@/lib/helpers/getProcessEnvManager";
|
||||
|
||||
import HomePage from "../page";
|
||||
|
||||
const mockUseSupabase = vi.fn(() => ({
|
||||
session: {
|
||||
user: {},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/context/SupabaseProvider", () => ({
|
||||
useSupabase: () => mockUseSupabase(),
|
||||
}));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
redirect: (url: string) => url,
|
||||
}));
|
||||
|
||||
describe("HomePage", () => {
|
||||
it("should render HomePage component properly", () => {
|
||||
const { overwriteEnvValuesWith, resetEnvValues } = getProcessEnvManager();
|
||||
|
@ -1,12 +1,20 @@
|
||||
import { redirect } from "next/navigation";
|
||||
"use client";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToPreviousPageOrChatPage } from "@/lib/helpers/redirectToPreviousPageOrChatPage";
|
||||
|
||||
import Features from "./Features";
|
||||
import Hero from "./Hero";
|
||||
|
||||
const HomePage = (): JSX.Element => {
|
||||
if (process.env.NEXT_PUBLIC_ENV === "local") {
|
||||
redirect("/upload");
|
||||
}
|
||||
const { session } = useSupabase();
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user !== undefined) {
|
||||
redirectToPreviousPageOrChatPage();
|
||||
}
|
||||
}, [session?.user]);
|
||||
|
||||
return (
|
||||
<main data-testid="home-page">
|
||||
|
@ -3,8 +3,8 @@ import { MentionData } from "@draft-js-plugins/mention";
|
||||
import { EditorState } from "draft-js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { requiredRolesForUpload } from "@/app/upload/config";
|
||||
import { mapMinimalBrainToMentionData } from "@/lib/components/MentionInput";
|
||||
import { requiredRolesForUpload } from "@/lib/config/upload";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { MentionInputMentionsType } from "../../types";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { requiredRolesForUpload } from "@/app/upload/config";
|
||||
import { requiredRolesForUpload } from "@/lib/config/upload";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
|
@ -124,7 +124,7 @@ export const useInvitation = () => {
|
||||
}
|
||||
} finally {
|
||||
setIsProcessingRequest(false);
|
||||
void router.push("/upload");
|
||||
void router.push("/chat");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,92 +0,0 @@
|
||||
"use client";
|
||||
import { UUID } from "crypto";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { isValidUrl } from "@/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/components/Crawler/helpers/isValidUrl";
|
||||
import { useCrawlApi } from "@/lib/api/crawl/useCrawlApi";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useCrawler = () => {
|
||||
const [isCrawling, setCrawling] = useState(false);
|
||||
const urlInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const { session } = useSupabase();
|
||||
const { publish } = useToast();
|
||||
const { track } = useEventTracking();
|
||||
|
||||
const { crawlWebsiteUrl } = useCrawlApi();
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
const crawlWebsite = useCallback(
|
||||
async (brainId: UUID | undefined) => {
|
||||
// Validate URL
|
||||
const url = urlInputRef.current ? urlInputRef.current.value : null;
|
||||
|
||||
if (url === null || !isValidUrl(url)) {
|
||||
console.log("Invalid URL");
|
||||
void track("URL_INVALID");
|
||||
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("invalidUrl", { ns: "upload" }),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure parameters
|
||||
const config = {
|
||||
url: url,
|
||||
js: false,
|
||||
depth: 1,
|
||||
max_pages: 100,
|
||||
max_time: 60,
|
||||
};
|
||||
|
||||
setCrawling(true);
|
||||
|
||||
console.log("Tracking URL_CRAWLED");
|
||||
void track("URL_CRAWLED");
|
||||
|
||||
try {
|
||||
console.log("Crawling website...", brainId);
|
||||
if (brainId !== undefined) {
|
||||
const response = await crawlWebsiteUrl({
|
||||
brainId,
|
||||
config,
|
||||
});
|
||||
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
text: response.data.message,
|
||||
});
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("crawlFailed", {
|
||||
message: JSON.stringify(error),
|
||||
ns: "upload",
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setCrawling(false);
|
||||
}
|
||||
},
|
||||
[crawlWebsiteUrl, publish, t, track]
|
||||
);
|
||||
|
||||
return {
|
||||
isCrawling,
|
||||
urlInputRef,
|
||||
crawlWebsite,
|
||||
};
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
"use client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Card from "@/lib/components/ui/Card";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
|
||||
import { useCrawler } from "./hooks/useCrawler";
|
||||
|
||||
export const Crawler = (): JSX.Element => {
|
||||
const { urlInputRef, isCrawling, crawlWebsite } = useCrawler();
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-center gap-5 px-6">
|
||||
<div className="max-w-xl w-full">
|
||||
<div className="flex-col justify-center gap-5">
|
||||
<Card className="h-32 flex gap-5 justify-center items-center px-5">
|
||||
<div className="text-center max-w-sm w-full flex flex-col gap-5 items-center">
|
||||
<Field
|
||||
name="crawlurl"
|
||||
ref={urlInputRef}
|
||||
type="text"
|
||||
placeholder={t("webSite", { ns: "upload" })}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center gap-5">
|
||||
<Button
|
||||
isLoading={isCrawling}
|
||||
onClick={() => void crawlWebsite(currentBrain?.id)}
|
||||
>
|
||||
{t("crawlButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,48 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import { motion } from "framer-motion";
|
||||
import { Dispatch, forwardRef, RefObject, SetStateAction } from "react";
|
||||
import { MdClose } from "react-icons/md";
|
||||
|
||||
interface FileComponentProps {
|
||||
file: File;
|
||||
setFiles: Dispatch<SetStateAction<File[]>>;
|
||||
}
|
||||
|
||||
const FileComponent = forwardRef(
|
||||
({ file, setFiles }: FileComponentProps, forwardedRef) => {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ x: "-10%", opacity: 0 }}
|
||||
animate={{ x: "0%", opacity: 1 }}
|
||||
exit={{ x: "10%", opacity: 0 }}
|
||||
layout
|
||||
ref={forwardedRef as RefObject<HTMLDivElement>}
|
||||
className="relative flex flex-row gap-1 py-2 dark:bg-black border-b last:border-none border-black/10 dark:border-white/25 leading-none px-6 overflow-hidden"
|
||||
>
|
||||
<div className="flex flex-1">
|
||||
<div className="flex flex-col">
|
||||
<span className="overflow-ellipsis overflow-hidden whitespace-nowrap">
|
||||
{file.name}
|
||||
</span>
|
||||
<span className="text-xs opacity-50 overflow-hidden text-ellipsis">
|
||||
{(file.size / 1000).toFixed(3)} kb
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
role="remove file"
|
||||
className="text-xl text-red-500 text-ellipsis absolute top-0 h-full right-0 flex items-center justify-center bg-white dark:bg-black p-3 shadow-md aspect-square"
|
||||
onClick={() =>
|
||||
setFiles((files) => files.filter((f) => f.name !== file.name))
|
||||
}
|
||||
>
|
||||
<MdClose />
|
||||
</button>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
FileComponent.displayName = "FileComponent";
|
||||
|
||||
export default FileComponent;
|
@ -1,164 +0,0 @@
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import axios from "axios";
|
||||
import { UUID } from "crypto";
|
||||
import { useCallback, useState } from "react";
|
||||
import { FileRejection, useDropzone } from "react-dropzone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useUploadApi } from "@/lib/api/upload/useUploadApi";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
import { useEventTracking } from "@/services/analytics/useEventTracking";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useFileUploader = () => {
|
||||
const { track } = useEventTracking();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { publish } = useToast();
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const { session } = useSupabase();
|
||||
const { uploadFile } = useUploadApi();
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
const { t } = useTranslation(["upload"]);
|
||||
|
||||
const upload = useCallback(
|
||||
async (file: File, brainId: UUID) => {
|
||||
const formData = new FormData();
|
||||
formData.append("uploadFile", file);
|
||||
try {
|
||||
void track("FILE_UPLOADED");
|
||||
const response = await uploadFile({ brainId, formData });
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
text:
|
||||
response.data.type === "success"
|
||||
? t("success", { ns: "upload" })
|
||||
: t("error", { message: response.data.message, ns: "upload" }),
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (axios.isAxiosError(e) && e.response?.status === 403) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `${JSON.stringify(
|
||||
(
|
||||
e.response as {
|
||||
data: { detail: string };
|
||||
}
|
||||
).data.detail
|
||||
)}`,
|
||||
});
|
||||
} else {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("error", { message: e, ns: "upload" }),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[publish, t, track, uploadFile]
|
||||
);
|
||||
|
||||
const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
if (fileRejections.length > 0) {
|
||||
const firstRejection = fileRejections[0];
|
||||
|
||||
if (firstRejection.errors[0].code === "file-invalid-type") {
|
||||
publish({ variant: "danger", text: t("invalidFileType") });
|
||||
} else {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("maxSizeError", { ns: "upload" }),
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < acceptedFiles.length; i++) {
|
||||
const file = acceptedFiles[i];
|
||||
const isAlreadyInFiles =
|
||||
files.filter((f) => f.name === file.name && f.size === file.size)
|
||||
.length > 0;
|
||||
if (isAlreadyInFiles) {
|
||||
publish({
|
||||
variant: "warning",
|
||||
text: t("alreadyAdded", { fileName: file.name, ns: "upload" }),
|
||||
});
|
||||
acceptedFiles.splice(i, 1);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
setFiles((files) => [...files, ...acceptedFiles]);
|
||||
};
|
||||
|
||||
const uploadAllFiles = async () => {
|
||||
if (files.length === 0) {
|
||||
publish({
|
||||
text: t("addFiles", { ns: "upload" }),
|
||||
variant: "warning",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
setIsPending(true);
|
||||
if (currentBrain?.id !== undefined) {
|
||||
await Promise.all(files.map((file) => upload(file, currentBrain.id)));
|
||||
setFiles([]);
|
||||
} else {
|
||||
publish({
|
||||
text: t("selectBrain", { ns: "upload" }),
|
||||
variant: "warning",
|
||||
});
|
||||
}
|
||||
setIsPending(false);
|
||||
};
|
||||
|
||||
const { getInputProps, getRootProps, isDragActive, open } = useDropzone({
|
||||
onDrop,
|
||||
noClick: true,
|
||||
maxSize: 100000000, // 1 MB
|
||||
accept: {
|
||||
"text/plain": [".txt"],
|
||||
"text/csv": [".csv"],
|
||||
"text/markdown": [".md", ".markdown"],
|
||||
"audio/x-m4a": [".m4a"],
|
||||
"audio/mpeg": [".mp3", ".mpga", ".mpeg"],
|
||||
"audio/webm": [".webm"],
|
||||
"video/mp4": [".mp4"],
|
||||
"audio/wav": [".wav"],
|
||||
"application/pdf": [".pdf"],
|
||||
"text/html": [".html"],
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||
[".pptx"],
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||
[".docx"],
|
||||
"application/vnd.oasis.opendocument.text": [".odt"],
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
|
||||
".xlsx",
|
||||
".xls",
|
||||
],
|
||||
"application/epub+zip": [".epub"],
|
||||
"application/x-ipynb+json": [".ipynb"],
|
||||
"text/x-python": [".py"],
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
isPending,
|
||||
getInputProps,
|
||||
getRootProps,
|
||||
isDragActive,
|
||||
open,
|
||||
uploadAllFiles,
|
||||
files,
|
||||
setFiles,
|
||||
};
|
||||
};
|
@ -1,74 +0,0 @@
|
||||
"use client";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Card from "@/lib/components/ui/Card";
|
||||
|
||||
import FileComponent from "./components/FileComponent";
|
||||
import { useFileUploader } from "./hooks/useFileUploader";
|
||||
|
||||
export const FileUploader = (): JSX.Element => {
|
||||
const {
|
||||
getInputProps,
|
||||
getRootProps,
|
||||
isDragActive,
|
||||
isPending,
|
||||
open,
|
||||
uploadAllFiles,
|
||||
files,
|
||||
setFiles,
|
||||
} = useFileUploader();
|
||||
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
return (
|
||||
<section
|
||||
{...getRootProps()}
|
||||
className="w-full outline-none flex flex-col gap-10 items-center justify-center px-6 py-3"
|
||||
>
|
||||
<div className="flex flex-col sm:flex-row max-w-3xl w-full items-center gap-5">
|
||||
<div className="flex-1 w-full">
|
||||
<Card className="h-52 flex justify-center items-center">
|
||||
<input {...getInputProps()} />
|
||||
<div className="text-center p-6 max-w-sm w-full flex flex-col gap-5 items-center">
|
||||
{isDragActive ? (
|
||||
<p className="text-blue-600">{t("drop", { ns: "upload" })}</p>
|
||||
) : (
|
||||
<button
|
||||
onClick={open}
|
||||
className="opacity-50 h-full cursor-pointer hover:opacity-100 hover:underline transition-opacity"
|
||||
>
|
||||
{t("dragAndDrop", { ns: "upload" })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{files.length > 0 && (
|
||||
<div className="flex-1 w-full">
|
||||
<Card className="h-52 py-3 overflow-y-auto">
|
||||
{files.length > 0 ? (
|
||||
<AnimatePresence mode="popLayout">
|
||||
{files.map((file) => (
|
||||
<FileComponent
|
||||
key={`${file.name} ${file.size}`}
|
||||
file={file}
|
||||
setFiles={setFiles}
|
||||
/>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<Button isLoading={isPending} onClick={() => void uploadAllFiles()}>
|
||||
{isPending ? t("uploadingButton") : t("uploadButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
@ -1,77 +0,0 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { Divider } from "@/lib/components/ui/Divider";
|
||||
import PageHeading from "@/lib/components/ui/PageHeading";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
import { redirectToLogin } from "@/lib/router/redirectToLogin";
|
||||
|
||||
import { Crawler } from "./Crawler";
|
||||
import { FileUploader } from "./FileUploader";
|
||||
import { requiredRolesForUpload } from "./config";
|
||||
|
||||
const UploadPage = (): JSX.Element => {
|
||||
const { currentBrain } = useBrainContext();
|
||||
const { session } = useSupabase();
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
if (session === null) {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
if (currentBrain === undefined) {
|
||||
return (
|
||||
<div className="flex justify-center items-center mt-5">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative max-w-md">
|
||||
<strong className="font-bold mr-1">
|
||||
{t("ohNo", { ns: "upload" })}
|
||||
</strong>
|
||||
<span className="block sm:inline">
|
||||
{t("selectBrainFirst", { ns: "upload" })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const hasUploadRights = requiredRolesForUpload.includes(currentBrain.role);
|
||||
|
||||
if (!hasUploadRights) {
|
||||
return (
|
||||
<div className="flex justify-center items-center mt-5">
|
||||
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative max-w-md">
|
||||
<strong className="font-bold mr-1">
|
||||
{t("ohNo", { ns: "upload" })}
|
||||
</strong>
|
||||
<span className="block sm:inline">
|
||||
{t("missingNecessaryRole", { ns: "upload" })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-10">
|
||||
<PageHeading
|
||||
title={t("title", { ns: "upload" })}
|
||||
subtitle={t("subtitle", { ns: "upload" })}
|
||||
/>
|
||||
<FileUploader />
|
||||
<Divider text={t("or")} className="m-5" />
|
||||
<Crawler />
|
||||
<div className="flex flex-col items-center justify-center gap-5 mt-5">
|
||||
<Link href={"/chat"}>
|
||||
<Button variant={"secondary"} className="py-3">
|
||||
{t("chatButton")}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
export default UploadPage;
|
@ -3,7 +3,7 @@ import Link from "next/link";
|
||||
|
||||
export const Logo = (): JSX.Element => {
|
||||
return (
|
||||
<Link href={"/"} className="flex items-center gap-4">
|
||||
<Link href={"/chat"} className="flex items-center gap-4">
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={"/logo.png"}
|
||||
|
@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
import Link from "next/link";
|
||||
import { Dispatch, HTMLAttributes, SetStateAction } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdPerson } from "react-icons/md";
|
||||
|
||||
import { useSupabase } from "@/lib/context/SupabaseProvider";
|
||||
@ -24,7 +23,6 @@ export const NavItems = ({
|
||||
}: NavItemsProps): JSX.Element => {
|
||||
const { session } = useSupabase();
|
||||
const isUserLoggedIn = session?.user !== undefined;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ul
|
||||
@ -34,19 +32,7 @@ export const NavItems = ({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{isUserLoggedIn ? (
|
||||
<>
|
||||
<NavLink setOpen={setOpen} to="/upload">
|
||||
{t("Upload")}
|
||||
</NavLink>
|
||||
<NavLink setOpen={setOpen} to="/chat">
|
||||
{t("Chat")}
|
||||
</NavLink>
|
||||
<NavLink setOpen={setOpen} to="/explore">
|
||||
{t("Explore")}
|
||||
</NavLink>
|
||||
</>
|
||||
) : (
|
||||
{!isUserLoggedIn && (
|
||||
<>
|
||||
<NavLink setOpen={setOpen} to="https://github.com/StanGirard/quivr">
|
||||
Github
|
||||
|
11
frontend/lib/helpers/redirectToPreviousPageOrChatPage.ts
Normal file
11
frontend/lib/helpers/redirectToPreviousPageOrChatPage.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const redirectToPreviousPageOrChatPage = (): void => {
|
||||
const previousPage = sessionStorage.getItem("previous-page");
|
||||
if (previousPage === null) {
|
||||
redirect("/chat");
|
||||
} else {
|
||||
sessionStorage.removeItem("previous-page");
|
||||
redirect(previousPage);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user