From 283c77d04b8702710242859b22522a22c49e49ab Mon Sep 17 00:00:00 2001 From: ruru <142723369+ruru-m07@users.noreply.github.com> Date: Sun, 19 May 2024 18:51:03 +0530 Subject: [PATCH] refactor(code): split code into smaller modules --- .env.example | 2 - action/index.ts | 71 ++++-------- app/layout.tsx | 6 +- app/page.tsx | 193 +------------------------------- components/errorComponent.tsx | 18 +++ components/formComponent.tsx | 75 +++++++++++++ components/home.tsx | 142 +++++++++++++++++++++++ components/loadingComponent.tsx | 12 ++ components/ui/badge.tsx | 36 ------ components/ui/textarea.tsx | 24 ---- utils/index.ts | 14 ++- utils/instructions.ts | 31 +++++ 12 files changed, 315 insertions(+), 309 deletions(-) create mode 100644 components/errorComponent.tsx create mode 100644 components/formComponent.tsx create mode 100644 components/home.tsx create mode 100644 components/loadingComponent.tsx delete mode 100644 components/ui/badge.tsx delete mode 100644 components/ui/textarea.tsx create mode 100644 utils/instructions.ts diff --git a/.env.example b/.env.example index c4d4268..fd6aa47 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,2 @@ # you can get your gemini api key from https://aistudio.google.com/app/apikey API_KEY=your_gemini_api_key_here - -VERCEL_URL="localhost:3000" diff --git a/action/index.ts b/action/index.ts index 614b8eb..8ec27b6 100644 --- a/action/index.ts +++ b/action/index.ts @@ -1,67 +1,35 @@ "use server"; -import { model } from "@/utils"; +import { handleModelResponse } from "@/utils"; +import { generateSystemInstruction } from "@/utils/instructions"; -export const commitChange = async ({ - message, - isEmojiSupport, -}: { +type ActionProps = { message: string | null; isEmojiSupport: boolean; -}): Promise<{ +}; + +type ActionPromise = { data: { text: string; } | null; error: string | null; -}> => { +}; + +export const commitChange = async ({ + message, + isEmojiSupport, +}: ActionProps): Promise => { if (!message || message.trim() === "") { return { data: null, error: "Please enter a message" }; } try { - const modelResponse = model.generateContent({ - contents: [{ role: "user", parts: [{ text: message }] }], - systemInstruction: `\ - You are an assistant that helps to provide Git commit messages based on https://www.conventionalcommits.org/en/v1.0.0/. - - - Provide a Git commit message in a code block as txt. - - Do not include the full command (e.g., \`git commit -m "here git message"\`). - - Only provide the commit message itself. - - Suggest 3 different commit messages to give the user some options. - - For example, if the user input is "I change lib folder to utils folder", then the output should be: - - ${ - isEmojiSupport && - `\ - - Use emojis in the commit message. - - For emojis, you can use https://gitmoji.dev/ - - Don't provide a description, just the commit message. - - For example, if the user input is "I change lib folder to utils folder", then the output should be: - ` - } - - ${ - isEmojiSupport - ? ` - \`\`\`txt \n ♻️ refactor(lib): change lib folder to utils folder \n\`\`\`\n - \`\`\`txt \n ➕ refactor(deps): rename lib folder to utils \n\`\`\`\n - \`\`\`txt \n ✏️ fix(deps): rename lib folder to utils \n\`\`\`\n - ` - : ` - \`\`\`txt \n refactor(lib): change lib folder to utils folder \n\`\`\`\n - \`\`\`txt \n refactor(deps): rename lib folder to utils \n\`\`\`\n - \`\`\`txt \n fix(deps): rename lib folder to utils \n\`\`\`\n - ` - } - - `, - }); - - const response = (await modelResponse).response.text(); + const systemInstruction = generateSystemInstruction(isEmojiSupport); + const responseText = await handleModelResponse(systemInstruction, message); console.log({ message: message, - response: response, + response: responseText, data: { time: new Date().toISOString(), }, @@ -69,12 +37,15 @@ export const commitChange = async ({ return { data: { - text: response, + text: responseText, }, error: null, }; - } catch (error) { + } catch (error: any) { console.log("error on action", error); - return { data: null, error: "something went wrong!" }; + return { + data: null, + error: error?.statusText ? error.statusText : "something went wrong!", + }; } }; diff --git a/app/layout.tsx b/app/layout.tsx index 2cac42b..327e55a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/provider/theme-provider"; import { Toaster } from "@/components/ui/toaster"; @@ -13,8 +12,6 @@ import Image from "next/image"; import NotificationBanner from "@/components/notificationBanner"; import { TooltipProvider } from "@/components/ui/tooltip"; -const inter = Inter({ subsets: ["latin"] }); - export const metadata: Metadata = { title: { default: siteConfig.name, @@ -55,7 +52,7 @@ export default function RootLayout({ }>) { return ( - + import("@/components/emptyScreen"), { - ssr: false, - loading: () => , -}); - -export default function Home() { - const [message, setMessage] = React.useState(null); - const [isLoading, setIsLoading] = React.useState(false); - const [error, setError] = React.useState(null); - const [commitChanges, setcommitChanges] = React.useState(null); - const [isEmojiSupport, setIsEmojiSupport] = React.useState(false); - const [commitMessages, setcommitMessages] = React.useState( - null - ); - - const { toast } = useToast(); - - const handelSubmit = async ({ - suggestion, - force = false, - }: { - suggestion: string; - force?: boolean; - }) => { - if (!force && suggestion === commitChanges) { - toast({ - variant: "destructive", - title: "Duplicate Message", - description: "Please enter a different message.", - }); - return; - } - - setIsLoading(true); - setError(null); - - try { - const { data, error } = await commitChange({ - message: suggestion, - isEmojiSupport: isEmojiSupport, - }); - - if (error) { - setError(error); - toast({ - variant: "destructive", - title: "Uh oh! Something went wrong.", - description: error, - action: ( - - ), - }); - } else { - if (data) { - setcommitMessages(data.text); - console.log(data.text); - setcommitChanges(suggestion); - setMessage(""); - } - } - } catch (error) { - console.log(error); - } finally { - setIsLoading(false); - } - }; - - const submitForm = ( - e: React.FormEvent, - message: string | null - ): void => { - e.preventDefault(); - handelSubmit({ suggestion: message || "" }); - }; - - return ( -
- -
-
- - - {error ? ( -
-

- Oops! Something Went Wrong!{" "} - : ( -

-

{error}

-
- ) : isLoading ? ( -
- -
- ) : commitMessages ? ( - - ) : ( -
- -
- )} -
-
-
- -
- -
submitForm(e, message)} - className="relative overflow-hidden rounded-lg border bg-primary-foreground/25 focus-within:ring-1 focus-within:ring-ring shadow" - > - - setMessage(e.target.value)} - value={message || ""} - disabled={isLoading} - autoComplete="off" - autoFocus - required - /> -
-
- setIsEmojiSupport(e)} - id="emoji-mode" - /> - -
- -
-
-
- -
- ); +export default function Page() { + return ; } diff --git a/components/errorComponent.tsx b/components/errorComponent.tsx new file mode 100644 index 0000000..b583aed --- /dev/null +++ b/components/errorComponent.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface ErrorComponentProps { + error: string; +} + +const ErrorComponent: React.FC = ({ error }) => { + return ( +
+

+ Oops! Something Went Wrong! : ( +

+

{error}

+
+ ); +}; + +export default ErrorComponent; diff --git a/components/formComponent.tsx b/components/formComponent.tsx new file mode 100644 index 0000000..27bc258 --- /dev/null +++ b/components/formComponent.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Spinner } from "@/components/ui/spinner"; +import { Switch } from "@/components/ui/switch"; +import { cn } from "@/lib/utils"; +import { CornerDownLeft } from "lucide-react"; + +interface FormComponentProps { + isLoading: boolean; + message: string | null; + setMessage: React.Dispatch>; + submitForm: ( + e: React.FormEvent, + message: string | null + ) => void; + setIsEmojiSupport: React.Dispatch>; +} + +const FormComponent: React.FC = ({ + isLoading, + message, + setMessage, + submitForm, + setIsEmojiSupport, +}) => { + return ( +
submitForm(e, message)} + className="relative overflow-hidden rounded-lg border bg-primary-foreground/25 focus-within:ring-1 focus-within:ring-ring shadow" + > + + setMessage(e.target.value)} + value={message || ""} + disabled={isLoading} + autoComplete="off" + autoFocus + required + /> +
+
+ setIsEmojiSupport(e)} + id="emoji-mode" + name="emoji-mode" + aria-label="Emoji Mode" + /> + +
+ +
+
+ ); +}; + +export default FormComponent; diff --git a/components/home.tsx b/components/home.tsx new file mode 100644 index 0000000..e7c39e7 --- /dev/null +++ b/components/home.tsx @@ -0,0 +1,142 @@ +"use client"; + +import React from "react"; +import dynamic from "next/dynamic"; +import { commitChange } from "@/action"; +import { Card } from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { toastVariants } from "@/components/ui/toast"; +import { useToast } from "@/components/ui/use-toast"; +import { cn } from "@/lib/utils"; +import ListSuggestion from "@/components/listSuggestion"; +import FormComponent from "./formComponent"; +import ErrorComponent from "./errorComponent"; +import LoadingComponent from "./loadingComponent"; +import Loader from "./loader"; + +const EmptyScreen = dynamic(() => import("@/components/emptyScreen"), { + ssr: false, + loading: () => , +}); + +export default function Home() { + const [message, setMessage] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + const [error, setError] = React.useState(null); + const [commitChanges, setCommitChanges] = React.useState(null); + const [isEmojiSupport, setIsEmojiSupport] = React.useState(false); + const [commitMessages, setCommitMessages] = React.useState( + null + ); + + const { toast } = useToast(); + + const handleSubmit = async ({ + suggestion, + force = false, + }: { + suggestion: string; + force?: boolean; + }) => { + if (!force && suggestion === commitChanges) { + toast({ + variant: "destructive", + title: "Duplicate Message", + description: "Please enter a different message.", + }); + return; + } + + setIsLoading(true); + setError(null); + + try { + const { data, error } = await commitChange({ + message: suggestion, + isEmojiSupport: isEmojiSupport, + }); + + if (error) { + setError(error); + toast({ + variant: "destructive", + title: "Uh oh! Something went wrong.", + description: error, + action: ( + + ), + }); + } else { + if (data) { + setCommitMessages(data.text); + console.log(data.text); + setCommitChanges(suggestion); + setMessage(""); + } + } + } catch (error) { + console.log(error); + } finally { + setIsLoading(false); + } + }; + + const submitForm = ( + e: React.FormEvent, + message: string | null + ): void => { + e.preventDefault(); + handleSubmit({ suggestion: message || "" }); + }; + + return ( +
+ +
+
+ + + {error ? ( + + ) : isLoading ? ( + + ) : commitMessages ? ( + + ) : ( +
+ +
+ )} +
+
+
+ +
+ + +
+ +
+ ); +} diff --git a/components/loadingComponent.tsx b/components/loadingComponent.tsx new file mode 100644 index 0000000..109367a --- /dev/null +++ b/components/loadingComponent.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import Loader from "@/components/loader"; + +const LoadingComponent: React.FC = () => { + return ( +
+ +
+ ); +}; + +export default LoadingComponent; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx deleted file mode 100644 index f795980..0000000 --- a/components/ui/badge.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from "react"; -import { cva, type VariantProps } from "class-variance-authority"; - -import { cn } from "@/lib/utils"; - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - }, -); - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -function Badge({ className, variant, ...props }: BadgeProps) { - return ( -
- ); -} - -export { Badge, badgeVariants }; diff --git a/components/ui/textarea.tsx b/components/ui/textarea.tsx deleted file mode 100644 index 0e546bb..0000000 --- a/components/ui/textarea.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from "react"; - -import { cn } from "@/lib/utils"; - -export interface TextareaProps - extends React.TextareaHTMLAttributes {} - -const Textarea = React.forwardRef( - ({ className, ...props }, ref) => { - return ( -