Chore: add husky, no-unused-vars & no-explicit-any eslint rules (#168)

* chore: add husky

* chore(eslint): add  no-unused-vars rule

* chore(eslint): add  no-explicit-any rule

* chore: add PR template
This commit is contained in:
Mamadou DICKO 2023-05-26 13:56:29 +02:00 committed by GitHub
parent 0ef223dbab
commit d848d5aa0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 185 additions and 174 deletions

16
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,16 @@
# Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context.
## Checklist before requesting a review
Please delete options that are not relevant.
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented hard-to-understand areas
- [ ] I have ideally added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged
## Screenshots (if appropriate):

11
.vscode/settings.json vendored
View File

@ -1,6 +1,9 @@
{
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none"
"[python]": {
"editor.defaultFormatter": "ms-python.autopep8"
},
"python.formatting.provider": "none",
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
}

View File

@ -1,3 +1,14 @@
{
"extends": "next/core-web-vitals"
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["@typescript-eslint"],
"parser": "@typescript-eslint/parser",
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "error"
}
}

View File

@ -1,12 +1,12 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { useSupabase } from "@/app/supabase-provider";
import Toast, { ToastRef } from "@/app/components/ui/Toast";
import PageHeading from "@/app/components/ui/PageHeading";
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 Link from "next/link";
import { useRouter } from "next/navigation";
import { useRef, useState } from "react";
export default function Logout() {
const { supabase } = useSupabase();

View File

@ -1,6 +1,4 @@
import { FC, ReactNode } from "react";
import PageHeading from "../components/ui/PageHeading";
import Card from "../components/ui/Card";
import {
GiArtificialIntelligence,
GiBrain,
@ -9,10 +7,9 @@ import {
GiLockedDoor,
GiOpenBook,
} from "react-icons/gi";
import Card from "../components/ui/Card";
interface FeaturesProps {}
const Features: FC<FeaturesProps> = ({}) => {
const Features: FC = () => {
return (
<section className="my-20 text-center flex flex-col items-center justify-center gap-10">
<div>

View File

@ -1,13 +1,11 @@
"use client";
import { motion, useScroll, useSpring, useTransform } from "framer-motion";
import Link from "next/link";
import { FC, useRef } from "react";
import Button from "../components/ui/Button";
import { MdNorthEast } from "react-icons/md";
import { motion, useScroll, useSpring, useTransform } from "framer-motion";
import Button from "../components/ui/Button";
interface HeroProps {}
const Hero: FC<HeroProps> = ({}) => {
const Hero: FC = () => {
const targetRef = useRef<HTMLDivElement | null>(null);
const { scrollYProgress } = useScroll({
target: targetRef,

View File

@ -1,14 +1,14 @@
"use client";
import { useState } from "react";
import axios from "axios";
import Card from "../components/ui/Card";
import Button from "../components/ui/Button";
import Modal from "../components/ui/Modal";
import { redirect } from "next/navigation";
import { useState } from "react";
import { MdSettings } from "react-icons/md";
import ChatMessages from "./ChatMessages";
import Button from "../components/ui/Button";
import Card from "../components/ui/Card";
import Modal from "../components/ui/Modal";
import PageHeading from "../components/ui/PageHeading";
import { useSupabase } from "../supabase-provider";
import { redirect } from "next/navigation";
import ChatMessages from "./ChatMessages";
export default function ChatPage() {
const [question, setQuestion] = useState("");
@ -17,7 +17,7 @@ export default function ChatPage() {
const [temperature, setTemperature] = useState(0);
const [maxTokens, setMaxTokens] = useState(500);
const [isPending, setIsPending] = useState(false);
const { supabase, session } = useSupabase();
const { session } = useSupabase();
if (session === null) {
redirect("/login");
}

View File

@ -1,11 +1,9 @@
"use client";
import { FC, useEffect, useLayoutEffect, useState } from "react";
import Button from "../ui/Button";
import { MdDarkMode, MdLightMode } from "react-icons/md";
import Button from "../ui/Button";
interface DarkModeToggleProps {}
const DarkModeToggle: FC<DarkModeToggleProps> = ({}) => {
const DarkModeToggle: FC = () => {
const [dark, setDark] = useState(false);
useLayoutEffect(() => {

View File

@ -1,13 +1,10 @@
import { FC, ReactNode, useState } from "react";
import * as Dialog from "@radix-ui/react-dialog";
import { MdClose, MdMenu } from "react-icons/md";
import { AnimatePresence, motion } from "framer-motion";
import Button from "../ui/Button";
import { FC, useState } from "react";
import { MdClose, MdMenu } from "react-icons/md";
import NavItems from "./NavItems";
interface MobileMenuProps {}
const MobileMenu: FC<MobileMenuProps> = ({}) => {
const MobileMenu: FC = () => {
const [open, setOpen] = useState(false);
return (
<Dialog.Root onOpenChange={setOpen} open={open}>

View File

@ -1,17 +1,13 @@
"use client";
import Image from "next/image";
import { FC, useEffect, useRef, useState } from "react";
import logo from "../../logo.png";
import Link from "next/link";
import Button from "../ui/Button";
import DarkModeToggle from "./DarkModeToggle";
import { motion } from "framer-motion";
import Link from "next/link";
import MobileMenu from "./MobileMenu";
import NavItems from "./NavItems";
interface NavBarProps {}
const NavBar: FC<NavBarProps> = ({}) => {
const NavBar: FC = () => {
const scrollPos = useRef<number>(0);
const [hidden, setHidden] = useState(false);
@ -42,7 +38,7 @@ const NavBar: FC<NavBarProps> = ({}) => {
<Link href={"/"} className="flex items-center gap-4">
<Image
className="rounded-full"
src={logo}
src={"/logo.png"}
alt="Quivr Logo"
width={48}
height={48}

View File

@ -1,12 +1,6 @@
import {
ButtonHTMLAttributes,
FC,
LegacyRef,
MutableRefObject,
forwardRef,
} from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";
import { ButtonHTMLAttributes, FC, LegacyRef, forwardRef } from "react";
import { FaSpinner } from "react-icons/fa";
const ButtonVariants = cva(

View File

@ -3,7 +3,7 @@ import { cn } from "@/lib/utils";
import { motion } from "framer-motion";
import { FC, HTMLAttributes, LegacyRef, forwardRef } from "react";
interface CardProps extends HTMLAttributes<HTMLDivElement> {}
type CardProps = HTMLAttributes<HTMLDivElement>;
const Card: FC<CardProps> = forwardRef(
({ children, className, ...props }, ref) => {

View File

@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import {
DetailedHTMLProps,
FC,
InputHTMLAttributes,
RefObject,
forwardRef,
@ -17,7 +16,7 @@ interface FieldProps
}
const Field = forwardRef(
({ label, className, name, id, ...props }: FieldProps, forwardedRef) => {
({ label, className, name, ...props }: FieldProps, forwardedRef) => {
return (
<fieldset className={cn("flex flex-col w-full", className)} name={name}>
{label && (

View File

@ -1,9 +1,7 @@
import { FC } from "react";
import { FaSpinner } from "react-icons/fa";
interface SpinnerProps {}
const Spinner: FC<SpinnerProps> = ({}) => {
const Spinner: FC = () => {
return <FaSpinner className="animate-spin m-5" />;
};

View File

@ -1,10 +1,10 @@
"use client";
import { ReactNode, forwardRef, useImperativeHandle, useState } from "react";
import * as ToastPrimitive from "@radix-ui/react-toast";
import Button from "./Button";
import { VariantProps, cva } from "class-variance-authority";
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;
@ -38,76 +38,74 @@ interface ToastProps
children?: ReactNode;
}
export const Toast = forwardRef(
({ children, variant, ...props }: ToastProps, forwardedRef) => {
const [toasts, setToasts] = useState<ToastContent[]>([]);
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;
});
},
const toggleToast = (value: boolean, index: number) => {
setToasts((toasts) =>
toasts.map((toast, i) => {
if (i === index) {
toast.open = value;
}
return toast;
})
);
};
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}
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 }))}
>
<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" />
</>
);
}
);
<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>;

View File

@ -1,5 +1,4 @@
import axios from "axios";
import { Document } from "../types";
import { useSupabase } from "../../supabase-provider";
interface DocumentDataProps {
@ -7,21 +6,22 @@ interface DocumentDataProps {
}
const DocumentData = async ({ documentName }: DocumentDataProps) => {
const { supabase, session } = useSupabase();
const { session } = useSupabase();
if (!session) {
throw new Error('User session not found');
throw new Error("User session not found");
}
const res = await axios.get(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/explore/${documentName}`,
{
headers: {
'Authorization': `Bearer ${session.access_token}`,
Authorization: `Bearer ${session.access_token}`,
},
}
);
// TODO: review the logic of this part and try to use unknown instead of any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const documents = res.data.documents as any[];
const doc = documents[0];
return (
<div className="prose">
<p>No. of documents: {documents.length}</p>

View File

@ -1,13 +1,13 @@
"use client";
import { Document } from "../types";
import Button from "../../components/ui/Button";
import Modal from "../../components/ui/Modal";
import { AnimatedCard } from "../../components/ui/Card";
import { Dispatch, SetStateAction, Suspense, useState } from "react";
import axios from "axios";
import DocumentData from "./DocumentData";
import Spinner from "@/app/components/ui/Spinner";
import { useSupabase } from "@/app/supabase-provider";
import axios from "axios";
import { Dispatch, SetStateAction, Suspense, useState } from "react";
import Button from "../../components/ui/Button";
import { AnimatedCard } from "../../components/ui/Card";
import Modal from "../../components/ui/Modal";
import { Document } from "../types";
import DocumentData from "./DocumentData";
interface DocumentProps {
document: Document;
@ -16,17 +16,16 @@ interface DocumentProps {
const DocumentItem = ({ document, setDocuments }: DocumentProps) => {
const [isDeleting, setIsDeleting] = useState(false);
const { supabase, session } = useSupabase()
const { session } = useSupabase();
if (!session) {
throw new Error('User session not found');
throw new Error("User session not found");
}
const deleteDocument = async (name: string) => {
setIsDeleting(true);
try {
console.log(`Deleting Document ${name}`);
const response = await axios.delete(
await axios.delete(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/explore/${name}`,
{
headers: {
@ -57,7 +56,7 @@ const DocumentItem = ({ document, setDocuments }: DocumentProps) => {
Trigger={<Button className="">View</Button>}
>
<Suspense fallback={<Spinner />}>
{/* @ts-expect-error */}
{/* @ts-expect-error TODO: check if DocumentData component can be sync */}
<DocumentData documentName={document.name} />
</Suspense>
</Modal>

View File

@ -1,19 +1,19 @@
"use client";
import { useState, useEffect } from "react";
import axios from "axios";
import { AnimatePresence } from "framer-motion";
import Link from "next/link";
import { redirect } from "next/navigation";
import { useEffect, useState } from "react";
import Button from "../components/ui/Button";
import Spinner from "../components/ui/Spinner";
import { useSupabase } from "../supabase-provider";
import DocumentItem from "./DocumentItem";
import { Document } from "./types";
import Button from "../components/ui/Button";
import Link from "next/link";
import Spinner from "../components/ui/Spinner";
import { AnimatePresence } from "framer-motion";
import { useSupabase } from "../supabase-provider";
import { redirect } from "next/navigation";
export default function ExplorePage() {
const [documents, setDocuments] = useState<Document[]>([]);
const [isPending, setIsPending] = useState(true);
const { supabase, session } = useSupabase();
const { session } = useSupabase();
if (session === null) {
redirect("/login");
}

View File

@ -1,25 +1,25 @@
"use client";
import { Message } from "@/lib/types";
import axios from "axios";
import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
import { redirect } from "next/navigation";
import {
Dispatch,
SetStateAction,
useCallback,
useState,
useRef,
useEffect,
useRef,
useState,
} from "react";
import { FileRejection, useDropzone } from "react-dropzone";
import axios from "axios";
import { Message } from "@/lib/types";
import Button from "../components/ui/Button";
import { MdClose } from "react-icons/md";
import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
import Button from "../components/ui/Button";
import Card from "../components/ui/Card";
import PageHeading from "../components/ui/PageHeading";
import { useSupabase } from "../supabase-provider";
import { redirect } from "next/navigation";
import Field from "../components/ui/Field";
import PageHeading from "../components/ui/PageHeading";
import Toast, { ToastRef } from "../components/ui/Toast";
import { useSupabase } from "../supabase-provider";
export default function UploadPage() {
const [message, setMessage] = useState<Message | null>(null);
@ -27,7 +27,7 @@ export default function UploadPage() {
const [files, setFiles] = useState<File[]>([]);
const [pendingFileIndex, setPendingFileIndex] = useState<number>(0);
const urlInputRef = useRef<HTMLInputElement | null>(null);
const { supabase, session } = useSupabase();
const { session } = useSupabase();
if (session === null) {
redirect("/login");
}
@ -83,10 +83,10 @@ export default function UploadPage() {
type: response.data.type,
text: response.data.message,
});
} catch (error: any) {
} catch (error: unknown) {
setMessage({
type: "error",
text: "Failed to crawl website: " + error.toString(),
text: "Failed to crawl website: " + JSON.stringify(error),
});
}
}, [session.access_token]);
@ -113,10 +113,10 @@ export default function UploadPage() {
? "File uploaded successfully: "
: "") + JSON.stringify(response.data.message),
});
} catch (error: any) {
} catch (error: unknown) {
setMessage({
type: "error",
text: "Failed to upload file: " + error.toString(),
text: "Failed to upload file: " + JSON.stringify(error),
});
}
},
@ -179,7 +179,7 @@ export default function UploadPage() {
<div className="text-center mt-2 p-6 max-w-sm w-full flex flex-col gap-5 items-center">
{files.length > 0 ? (
<AnimatePresence>
{files.map((file, i) => (
{files.map((file) => (
<FileComponent
key={file.name + file.size}
file={file}

View File

@ -1,4 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {};
module.exports = nextConfig
module.exports = nextConfig;

View File

@ -6,7 +6,10 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test-type": "tsc --noEmit --emitDeclarationOnly false",
"test": "yarn test-type",
"precommit": "yarn lint && yarn test"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.3",
@ -18,13 +21,14 @@
"@types/node": "20.1.7",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"@typescript-eslint/eslint-plugin": "^5.59.7",
"@vercel/analytics": "^1.0.1",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"encoding": "^0.1.13",
"eslint": "8.40.0",
"eslint": "^8.41.0",
"eslint-config-next": "13.4.2",
"framer-motion": "^10.12.12",
"next": "13.4.2",
@ -36,11 +40,12 @@
"rehype-highlight": "^6.0.0",
"tailwind-merge": "^1.12.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
"typescript": "^5.0.4"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@types/next": "^9.0.0",
"husky": "^8.0.3",
"react-icons": "^4.8.0"
}
}

View File

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 277 KiB

View File

@ -1,7 +1,9 @@
{
"compilerOptions": {
"typeRoots": ["node_modules/@types"],
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,