mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-25 04:12:44 +03:00
feat(toast): add global publisher (#177)
This commit is contained in:
parent
d44e9e1984
commit
e388990384
@ -1,11 +1,11 @@
|
||||
import { useToast } from "@/app/hooks/useToast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import { useState } from "react";
|
||||
|
||||
export const useGoogleLogin = () => {
|
||||
const { supabase } = useSupabase();
|
||||
|
||||
const { setMessage, messageToast } = useToast();
|
||||
const { publish } = useToast();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const signInWithGoogle = async () => {
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
@ -19,8 +19,8 @@ export const useGoogleLogin = () => {
|
||||
});
|
||||
setIsPending(false);
|
||||
if (error) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "An error occurred ",
|
||||
});
|
||||
}
|
||||
@ -29,6 +29,5 @@ export const useGoogleLogin = () => {
|
||||
return {
|
||||
signInWithGoogle,
|
||||
isPending,
|
||||
messageToast,
|
||||
};
|
||||
};
|
||||
|
@ -1,12 +1,11 @@
|
||||
import Button from "@/app/components/ui/Button";
|
||||
import Toast from "@/app/components/ui/Toast";
|
||||
|
||||
import { useGoogleLogin } from "./hooks/useGoogleLogin";
|
||||
|
||||
export const GoogleLoginButton = () => {
|
||||
const { isPending, messageToast, signInWithGoogle } = useGoogleLogin();
|
||||
const { isPending, signInWithGoogle } = useGoogleLogin();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={signInWithGoogle}
|
||||
isLoading={isPending}
|
||||
@ -15,7 +14,5 @@ export const GoogleLoginButton = () => {
|
||||
>
|
||||
Login with Google
|
||||
</Button>
|
||||
<Toast ref={messageToast} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,7 @@
|
||||
"use client";
|
||||
import Button from "@/app/components/ui/Button";
|
||||
import Toast from "@/app/components/ui/Toast";
|
||||
import { useToast } from "@/app/hooks/useToast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import { useState } from "react";
|
||||
|
||||
type MaginLinkLoginProps = {
|
||||
@ -14,12 +13,12 @@ export const MagicLinkLogin = ({ email, setEmail }: MaginLinkLoginProps) => {
|
||||
const { supabase } = useSupabase();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const { setMessage, messageToast } = useToast();
|
||||
const { publish } = useToast();
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (email === "") {
|
||||
setMessage({
|
||||
type: "error",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Please enter your email address",
|
||||
});
|
||||
return;
|
||||
@ -35,13 +34,13 @@ export const MagicLinkLogin = ({ email, setEmail }: MaginLinkLoginProps) => {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setMessage({
|
||||
type: "danger",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: error.message,
|
||||
});
|
||||
} else {
|
||||
setMessage({
|
||||
type: "success",
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Magic link sent successfully if email recognized",
|
||||
});
|
||||
|
||||
@ -51,7 +50,6 @@ export const MagicLinkLogin = ({ email, setEmail }: MaginLinkLoginProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
variant={"tertiary"}
|
||||
@ -60,7 +58,5 @@ export const MagicLinkLogin = ({ email, setEmail }: MaginLinkLoginProps) => {
|
||||
>
|
||||
Send Magic Link
|
||||
</Button>
|
||||
<Toast ref={messageToast} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -4,9 +4,8 @@ import Card from "@/app/components/ui/Card";
|
||||
import { Divider } from "@/app/components/ui/Divider";
|
||||
import Field from "@/app/components/ui/Field";
|
||||
import PageHeading from "@/app/components/ui/PageHeading";
|
||||
import Toast from "@/app/components/ui/Toast";
|
||||
import { useToast } from "@/app/hooks/useToast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
@ -19,7 +18,7 @@ export default function Login() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const { setMessage, messageToast } = useToast();
|
||||
const { publish } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@ -31,17 +30,15 @@ export default function Login() {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setMessage({
|
||||
type: "danger",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: error.message,
|
||||
});
|
||||
} else if (data) {
|
||||
setMessage({
|
||||
type: "success",
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Successfully logged in",
|
||||
});
|
||||
} else {
|
||||
setEmail("");
|
||||
}
|
||||
setIsPending(false);
|
||||
};
|
||||
@ -68,6 +65,7 @@ export default function Login() {
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
value={email}
|
||||
/>
|
||||
<Field
|
||||
name="password"
|
||||
@ -92,7 +90,6 @@ export default function Login() {
|
||||
</form>
|
||||
</Card>
|
||||
</section>
|
||||
<Toast ref={messageToast} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -2,19 +2,17 @@
|
||||
import Button from "@/app/components/ui/Button";
|
||||
import Card from "@/app/components/ui/Card";
|
||||
import PageHeading from "@/app/components/ui/PageHeading";
|
||||
import Toast, { ToastRef } from "@/app/components/ui/Toast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Logout() {
|
||||
const { supabase } = useSupabase();
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const logoutToast = useRef<ToastRef>(null);
|
||||
const [error, setError] = useState("Unknown Error");
|
||||
|
||||
const { publish } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
const handleLogout = async () => {
|
||||
@ -23,14 +21,12 @@ export default function Logout() {
|
||||
|
||||
if (error) {
|
||||
console.error("Error logging out:", error.message);
|
||||
setError(error.message);
|
||||
logoutToast.current?.publish({
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `Error logging out: ${error.message}`,
|
||||
});
|
||||
} else {
|
||||
console.log("User logged out");
|
||||
logoutToast.current?.publish({
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Logged out successfully",
|
||||
});
|
||||
@ -39,10 +35,6 @@ export default function Logout() {
|
||||
setIsPending(false);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// handleLogout();
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<section className="w-full min-h-screen h-full outline-none flex flex-col gap-5 items-center justify-center p-6">
|
||||
@ -63,12 +55,6 @@ export default function Logout() {
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
<Toast variant="success" ref={logoutToast}>
|
||||
Logged Out Successfully
|
||||
</Toast>
|
||||
<Toast variant="danger" ref={logoutToast}>
|
||||
{error}
|
||||
</Toast>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -3,10 +3,10 @@ import Button from "@/app/components/ui/Button";
|
||||
import Card from "@/app/components/ui/Card";
|
||||
import Field from "@/app/components/ui/Field";
|
||||
import PageHeading from "@/app/components/ui/PageHeading";
|
||||
import Toast, { ToastRef } from "@/app/components/ui/Toast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import Link from "next/link";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function SignUp() {
|
||||
const { supabase } = useSupabase();
|
||||
@ -14,8 +14,7 @@ export default function SignUp() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const signupToast = useRef<ToastRef>(null);
|
||||
|
||||
const { publish } = useToast();
|
||||
const handleSignUp = async () => {
|
||||
setIsPending(true);
|
||||
const { data, error } = await supabase.auth.signUp({
|
||||
@ -25,13 +24,15 @@ export default function SignUp() {
|
||||
|
||||
if (error) {
|
||||
console.error("Error signing up:", error.message);
|
||||
signupToast.current?.publish({
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `Error signing up: ${error.message}`,
|
||||
});
|
||||
} else if (data) {
|
||||
console.log("User signed up");
|
||||
signupToast.current?.publish({ variant: "success", text: "Sign" });
|
||||
publish({
|
||||
variant: "success",
|
||||
text: "Confirmation Email sent, please check your email",
|
||||
});
|
||||
}
|
||||
setIsPending(false);
|
||||
};
|
||||
@ -70,11 +71,6 @@ export default function SignUp() {
|
||||
</form>
|
||||
</Card>
|
||||
</section>
|
||||
<Toast variant="success" ref={signupToast}>
|
||||
<h1 className="font-bold">Confirmation Email sent</h1>
|
||||
<p className="text-sm">Check your email.</p>
|
||||
</Toast>
|
||||
<Toast variant="danger" ref={signupToast} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
@ -41,8 +41,6 @@ export default function ChatPage() {
|
||||
}
|
||||
);
|
||||
setHistory(response.data.history);
|
||||
console.log(response.data.history);
|
||||
|
||||
setQuestion("");
|
||||
setIsPending(false);
|
||||
};
|
||||
|
@ -15,7 +15,9 @@ const Divider: FC<DividerProps> = forwardRef(
|
||||
>
|
||||
<hr className="border-t border-gray-300 w-12" />
|
||||
{text !== undefined && (
|
||||
<p className="px-3 bg-white text-center text-gray-500">{text}</p>
|
||||
<p className="px-3 text-center text-gray-500 dark:text-white">
|
||||
{text}
|
||||
</p>
|
||||
)}
|
||||
<hr className="border-t border-gray-300 w-12" />
|
||||
</div>
|
||||
|
@ -1,116 +0,0 @@
|
||||
"use client";
|
||||
import { cn, generateUniqueId } from "@/lib/utils";
|
||||
import * as ToastPrimitive from "@radix-ui/react-toast";
|
||||
import { VariantProps, cva } from "class-variance-authority";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ReactNode, forwardRef, useImperativeHandle, useState } from "react";
|
||||
import Button from "./Button";
|
||||
|
||||
export interface ToastRef {
|
||||
publish: (toast: ToastContent) => void;
|
||||
}
|
||||
|
||||
const ToastVariants = cva(
|
||||
"bg-white dark:bg-black px-8 max-w-sm w-full py-5 border border-black/10 dark:border-white/25 rounded-xl shadow-xl flex items-center pointer-events-auto data-[swipe=end]:opacity-0 data-[state=closed]:opacity-0 transition-opacity",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
neutral: "",
|
||||
danger: "bg-red-400 dark:bg-red-600",
|
||||
success: "bg-green-400 dark:bg-green-600",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface ToastContent extends VariantProps<typeof ToastVariants> {
|
||||
text: string;
|
||||
open?: boolean;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
interface ToastProps
|
||||
extends ToastPrimitive.ToastProps,
|
||||
VariantProps<typeof ToastVariants> {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const Toast = forwardRef(({ ...props }: ToastProps, forwardedRef) => {
|
||||
const [toasts, setToasts] = useState<ToastContent[]>([]);
|
||||
|
||||
const toggleToast = (value: boolean, index: number) => {
|
||||
setToasts((toasts) =>
|
||||
toasts.map((toast, i) => {
|
||||
if (i === index) {
|
||||
toast.open = value;
|
||||
}
|
||||
return toast;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
useImperativeHandle(
|
||||
forwardedRef,
|
||||
(): ToastRef => ({
|
||||
publish: (toast: ToastContent) => {
|
||||
setToasts((toasts) => {
|
||||
const newToasts = [...toasts];
|
||||
toast.open = true;
|
||||
toast.id = generateUniqueId();
|
||||
newToasts.push(toast);
|
||||
return newToasts;
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<AnimatePresence mode="popLayout">
|
||||
{toasts.map((toast, index) => {
|
||||
if (!toast.open) return;
|
||||
return (
|
||||
<ToastPrimitive.Root
|
||||
open={toast.open}
|
||||
onOpenChange={(value) => toggleToast(value, index)}
|
||||
asChild
|
||||
forceMount
|
||||
key={toast.id}
|
||||
{...props}
|
||||
>
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ x: "100%", opacity: 0 }}
|
||||
animate={{
|
||||
x: "0%",
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
className={cn(ToastVariants({ variant: toast.variant }))}
|
||||
>
|
||||
<ToastPrimitive.Description className="flex-1">
|
||||
{toast.text}
|
||||
</ToastPrimitive.Description>
|
||||
<ToastPrimitive.Close asChild>
|
||||
<Button variant={"tertiary"}>Dismiss</Button>
|
||||
</ToastPrimitive.Close>
|
||||
</motion.div>
|
||||
</ToastPrimitive.Root>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
<ToastPrimitive.Viewport className="fixed flex-col bottom-0 left-0 right-0 p-5 flex items-end gap-2 outline-none pointer-events-none" />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const ToastProvider = ({ children }: { children?: ReactNode }) => {
|
||||
return <ToastPrimitive.Provider>{children}</ToastPrimitive.Provider>;
|
||||
};
|
||||
|
||||
Toast.displayName = "Toast";
|
||||
|
||||
export default Toast;
|
60
frontend/app/components/ui/Toast/components/Toast.tsx
Normal file
60
frontend/app/components/ui/Toast/components/Toast.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as ToastPrimitive from "@radix-ui/react-toast";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { ReactNode } from "react";
|
||||
import Button from "../../Button";
|
||||
import { ToastContext } from "../domain/ToastContext";
|
||||
import { ToastVariants } from "../domain/types";
|
||||
import { useToastBuilder } from "../hooks/useToastBuilder";
|
||||
|
||||
export const Toast = ({
|
||||
children,
|
||||
...toastProviderProps
|
||||
}: { children?: ReactNode } & ToastPrimitive.ToastProviderProps) => {
|
||||
const { publish, toasts, toggleToast } = useToastBuilder();
|
||||
return (
|
||||
<ToastPrimitive.Provider {...toastProviderProps}>
|
||||
<ToastContext.Provider value={{ publish }}>
|
||||
{children}
|
||||
<AnimatePresence mode="popLayout">
|
||||
{toasts.map((toast, index) => {
|
||||
if (!toast.open) return;
|
||||
return (
|
||||
<ToastPrimitive.Root
|
||||
open={toast.open}
|
||||
onOpenChange={(value) => toggleToast(value, index)}
|
||||
asChild
|
||||
forceMount
|
||||
key={toast.id}
|
||||
>
|
||||
<motion.div
|
||||
layout
|
||||
initial={{ x: "100%", opacity: 0 }}
|
||||
animate={{
|
||||
x: "0%",
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{ opacity: 0 }}
|
||||
className={cn(ToastVariants({ variant: toast.variant }))}
|
||||
>
|
||||
<ToastPrimitive.Description className="flex-1">
|
||||
{toast.text}
|
||||
</ToastPrimitive.Description>
|
||||
<ToastPrimitive.Close asChild>
|
||||
<Button variant={"tertiary"} className="text-white">
|
||||
Dismiss
|
||||
</Button>
|
||||
</ToastPrimitive.Close>
|
||||
</motion.div>
|
||||
</ToastPrimitive.Root>
|
||||
);
|
||||
})}
|
||||
</AnimatePresence>
|
||||
<ToastPrimitive.Viewport className="fixed flex-col bottom-0 left-0 right-0 p-5 flex items-end gap-2 outline-none pointer-events-none" />
|
||||
</ToastContext.Provider>
|
||||
</ToastPrimitive.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Toast.displayName = "Toast";
|
11
frontend/app/components/ui/Toast/components/index.tsx
Normal file
11
frontend/app/components/ui/Toast/components/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
import * as ToastPrimitive from "@radix-ui/react-toast";
|
||||
import { ReactNode } from "react";
|
||||
import { Toast } from "./Toast";
|
||||
|
||||
export const ToastProvider = ({
|
||||
children,
|
||||
...toastProviderProps
|
||||
}: { children?: ReactNode } & ToastPrimitive.ToastProviderProps) => {
|
||||
return <Toast {...toastProviderProps}>{children}</Toast>;
|
||||
};
|
6
frontend/app/components/ui/Toast/domain/ToastContext.ts
Normal file
6
frontend/app/components/ui/Toast/domain/ToastContext.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { createContext } from "react";
|
||||
import { ToastPublisher } from "./types";
|
||||
|
||||
const publish: ToastPublisher = () => void 0;
|
||||
|
||||
export const ToastContext = createContext({ publish });
|
32
frontend/app/components/ui/Toast/domain/types.ts
Normal file
32
frontend/app/components/ui/Toast/domain/types.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { VariantProps, cva } from "class-variance-authority";
|
||||
|
||||
export const ToastVariants = cva(
|
||||
"bg-white dark:bg-black px-8 max-w-sm w-full py-5 border border-black/10 dark:border-white/25 rounded-xl shadow-xl flex items-center pointer-events-auto data-[swipe=end]:opacity-0 data-[state=closed]:opacity-0 transition-opacity",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
neutral: "bg-gray-500 dark:bg-gray-600",
|
||||
danger: "bg-red-500 text-white dark:bg-red-600",
|
||||
success: "bg-green-500 text-white dark:bg-green-600",
|
||||
warning: "bg-orange-500 text-white dark:bg-orange-600",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type ToastVariant = NonNullable<VariantProps<typeof ToastVariants>["variant"]>;
|
||||
|
||||
export interface ToastData {
|
||||
text: string;
|
||||
variant: ToastVariant;
|
||||
}
|
||||
|
||||
export interface ToastContent extends ToastData {
|
||||
open?: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ToastPublisher = (toast: ToastData) => void;
|
@ -0,0 +1,5 @@
|
||||
export const generateToastUniqueId = () => {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 10000);
|
||||
return `${timestamp}-${random}`;
|
||||
};
|
32
frontend/app/components/ui/Toast/hooks/useToastBuilder.ts
Normal file
32
frontend/app/components/ui/Toast/hooks/useToastBuilder.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { useState } from "react";
|
||||
import { ToastContent, ToastData, ToastPublisher } from "../domain/types";
|
||||
import { generateToastUniqueId } from "../helpers/generateToastUniqueId";
|
||||
|
||||
// ⚠️ You should not probably import this. use `useToast` instead
|
||||
export const useToastBuilder = () => {
|
||||
const [toasts, setToasts] = useState<ToastContent[]>([]);
|
||||
|
||||
const toggleToast = (value: boolean, index: number) => {
|
||||
setToasts((toasts) =>
|
||||
toasts.map((toast, i) => {
|
||||
if (i === index) {
|
||||
toast.open = value;
|
||||
}
|
||||
return toast;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const publish: ToastPublisher = (newTost: ToastData) => {
|
||||
setToasts((toasts) => [
|
||||
...toasts,
|
||||
{ ...newTost, open: true, id: generateToastUniqueId() },
|
||||
]);
|
||||
};
|
||||
|
||||
return {
|
||||
publish,
|
||||
toggleToast,
|
||||
toasts,
|
||||
};
|
||||
};
|
1
frontend/app/components/ui/Toast/index.ts
Normal file
1
frontend/app/components/ui/Toast/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./components/index";
|
@ -24,7 +24,6 @@ const DocumentItem = ({ document, setDocuments }: DocumentProps) => {
|
||||
const deleteDocument = async (name: string) => {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
console.log(`Deleting Document ${name}`);
|
||||
await axios.delete(
|
||||
`${process.env.NEXT_PUBLIC_BACKEND_URL}/explore/${name}`,
|
||||
{
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { Message } from "postcss";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ToastRef } from "../components/ui/Toast";
|
||||
|
||||
export const useToast = () => {
|
||||
const [message, setMessage] = useState<Message | null>(null);
|
||||
const messageToast = useRef<ToastRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!message) return;
|
||||
messageToast.current?.publish({
|
||||
variant:
|
||||
message.type === "error"
|
||||
? "danger"
|
||||
: message.type === "warning"
|
||||
? "neutral"
|
||||
: "success",
|
||||
text: message.text,
|
||||
});
|
||||
}, [message]);
|
||||
return {
|
||||
setMessage,
|
||||
messageToast,
|
||||
};
|
||||
};
|
@ -1,15 +1,15 @@
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import axios from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useToast } from "../../../../hooks/useToast";
|
||||
import { isValidUrl } from "../helpers/isValidUrl";
|
||||
|
||||
export const useCrawler = () => {
|
||||
const [isCrawling, setCrawling] = useState(false);
|
||||
const urlInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const { setMessage, messageToast } = useToast();
|
||||
const { session } = useSupabase();
|
||||
const { publish } = useToast();
|
||||
if (session === null) {
|
||||
redirect("/login");
|
||||
}
|
||||
@ -21,8 +21,8 @@ export const useCrawler = () => {
|
||||
|
||||
if (!url || !isValidUrl(url)) {
|
||||
// Assuming you have a function to validate URLs
|
||||
setMessage({
|
||||
type: "error",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Invalid URL",
|
||||
});
|
||||
setCrawling(false);
|
||||
@ -49,13 +49,13 @@ export const useCrawler = () => {
|
||||
}
|
||||
);
|
||||
|
||||
setMessage({
|
||||
type: response.data.type,
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
text: response.data.message,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Failed to crawl website: " + JSON.stringify(error),
|
||||
});
|
||||
} finally {
|
||||
@ -66,7 +66,7 @@ export const useCrawler = () => {
|
||||
return {
|
||||
isCrawling,
|
||||
urlInputRef,
|
||||
messageToast,
|
||||
|
||||
crawlWebsite,
|
||||
};
|
||||
};
|
||||
|
@ -2,11 +2,10 @@
|
||||
import Button from "@/app/components/ui/Button";
|
||||
import Card from "@/app/components/ui/Card";
|
||||
import Field from "@/app/components/ui/Field";
|
||||
import Toast from "@/app/components/ui/Toast";
|
||||
import { useCrawler } from "./hooks/useCrawler";
|
||||
|
||||
export const Crawler = (): JSX.Element => {
|
||||
const { urlInputRef, isCrawling, messageToast, crawlWebsite } = useCrawler();
|
||||
const { urlInputRef, isCrawling, crawlWebsite } = useCrawler();
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex justify-center gap-5">
|
||||
@ -29,7 +28,6 @@ export const Crawler = (): JSX.Element => {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<Toast ref={messageToast} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useToast } from "@/app/hooks/useToast";
|
||||
import { useSupabase } from "@/app/supabase-provider";
|
||||
import { useToast } from "@/lib/hooks/useToast";
|
||||
import axios from "axios";
|
||||
import { redirect } from "next/navigation";
|
||||
import { useCallback, useState } from "react";
|
||||
@ -7,7 +7,7 @@ import { FileRejection, useDropzone } from "react-dropzone";
|
||||
|
||||
export const useFileUploader = () => {
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
const { messageToast, setMessage } = useToast();
|
||||
const { publish } = useToast();
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [pendingFileIndex, setPendingFileIndex] = useState<number>(0);
|
||||
const { session } = useSupabase();
|
||||
@ -31,16 +31,16 @@ export const useFileUploader = () => {
|
||||
}
|
||||
);
|
||||
|
||||
setMessage({
|
||||
type: response.data.type,
|
||||
publish({
|
||||
variant: response.data.type,
|
||||
text:
|
||||
(response.data.type === "success"
|
||||
? "File uploaded successfully: "
|
||||
: "") + JSON.stringify(response.data.message),
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: "Failed to upload file: " + JSON.stringify(error),
|
||||
});
|
||||
}
|
||||
@ -50,18 +50,18 @@ export const useFileUploader = () => {
|
||||
|
||||
const onDrop = (acceptedFiles: File[], fileRejections: FileRejection[]) => {
|
||||
if (fileRejections.length > 0) {
|
||||
setMessage({ type: "error", text: "File too big." });
|
||||
publish({ variant: "danger", text: "File too big." });
|
||||
return;
|
||||
}
|
||||
setMessage(null);
|
||||
|
||||
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) {
|
||||
setMessage({
|
||||
type: "warning",
|
||||
publish({
|
||||
variant: "warning",
|
||||
text: `${file.name} was already added`,
|
||||
});
|
||||
acceptedFiles.splice(i, 1);
|
||||
@ -71,9 +71,15 @@ export const useFileUploader = () => {
|
||||
};
|
||||
|
||||
const uploadAllFiles = async () => {
|
||||
if (files.length === 0) {
|
||||
publish({
|
||||
text: "Please, add files to upload",
|
||||
variant: "warning",
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsPending(true);
|
||||
setMessage(null);
|
||||
// files.forEach((file) => upload(file));
|
||||
|
||||
for (const file of files) {
|
||||
await upload(file);
|
||||
setPendingFileIndex((i) => i + 1);
|
||||
@ -96,7 +102,7 @@ export const useFileUploader = () => {
|
||||
open,
|
||||
uploadAllFiles,
|
||||
pendingFileIndex,
|
||||
messageToast,
|
||||
|
||||
files,
|
||||
setFiles,
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
"use client";
|
||||
import Toast from "@/app/components/ui/Toast";
|
||||
import { AnimatePresence } from "framer-motion";
|
||||
import Button from "../../../components/ui/Button";
|
||||
import Card from "../../../components/ui/Card";
|
||||
@ -12,7 +11,6 @@ export const FileUploader = (): JSX.Element => {
|
||||
getRootProps,
|
||||
isDragActive,
|
||||
isPending,
|
||||
messageToast,
|
||||
open,
|
||||
pendingFileIndex,
|
||||
uploadAllFiles,
|
||||
@ -21,7 +19,6 @@ export const FileUploader = (): JSX.Element => {
|
||||
} = useFileUploader();
|
||||
|
||||
return (
|
||||
<>
|
||||
<section
|
||||
{...getRootProps()}
|
||||
className="w-full outline-none flex flex-col gap-5 items-center justify-center p-6"
|
||||
@ -67,14 +64,10 @@ export const FileUploader = (): JSX.Element => {
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center mt-5">
|
||||
<Button isLoading={isPending} onClick={uploadAllFiles}>
|
||||
{isPending
|
||||
? `Uploading ${files[pendingFileIndex].name}`
|
||||
: "Upload"}
|
||||
{isPending ? `Uploading ${files[pendingFileIndex].name}` : "Upload"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<Toast ref={messageToast} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
10
frontend/lib/hooks/useToast.ts
Normal file
10
frontend/lib/hooks/useToast.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { ToastContext } from "../../app/components/ui/Toast/domain/ToastContext";
|
||||
|
||||
export const useToast = () => {
|
||||
const { publish } = useContext(ToastContext);
|
||||
|
||||
return {
|
||||
publish,
|
||||
};
|
||||
};
|
@ -4,9 +4,3 @@ import { twMerge } from "tailwind-merge";
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function generateUniqueId() {
|
||||
const timestamp = Date.now();
|
||||
const random = Math.floor(Math.random() * 10000);
|
||||
return `${timestamp}-${random}`;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user