diff --git a/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md b/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md index d3a0e1b..fcb8e75 100644 --- a/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/PULL_REQUEST_TEMPLATE.md @@ -19,14 +19,14 @@ Closes #[issue_number] ## Checklist -- [ ] My code follows the projectโ€™s style guidelines -- [ ] I have performed a self-review of my code -- [ ] I have commented my code where necessary -- [ ] I have added tests that prove my fix is effective or my feature works +- [ ] My code follows the projectโ€™s style guidelines +- [ ] I have performed a self-review of my code +- [ ] I have commented my code where necessary +- [ ] I have added tests that prove my fix is effective or my feature works - [ ] All new and existing tests pass --- ## Additional Notes -Any extra context or screenshots (if applicable). \ No newline at end of file +Any extra context or screenshots (if applicable). diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 8130eba..b46e40e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: bug assignees: '' - --- Found a bug? Please fill out the sections below. ๐Ÿ‘ diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 559d6dd..43c7fca 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: Feature assignees: '' - --- ### Is your proposal related to a problem? diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 0000000..c77e225 --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,20 @@ +name: Prettier Check + +on: + pull_request: + branches: [main] + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Install dependencies + run: npm install + - name: Run Prettier check + run: npx prettier --check . diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..0bc94d8 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules +dist +build +coverage +.env +*.min.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a261b7c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": true, + "printWidth": 80, + "trailingComma": "es5" +} diff --git a/README.md b/README.md index 1607677..8bf0b20 100644 --- a/README.md +++ b/README.md @@ -5,41 +5,46 @@ Frontend of the RoastAI project, built with **React, TypeScript, and Vite**, pro --- ## Project Overview / Purpose -This frontend allows users to: -- Chat with **SavageBot**, a witty, roast-style AI. -- Receive humorous responses from the AI based on user input. -- Browse funny debates and quote remixes. + +This frontend allows users to: + +- Chat with **SavageBot**, a witty, roast-style AI. +- Receive humorous responses from the AI based on user input. +- Browse funny debates and quote remixes. It communicates with the RoastAI backend to fetch AI responses and display them in a responsive, stylish interface. --- ## Features -- Interactive **chat with SavageBot** -- **Funny debates** and quote remix features -- Fully **responsive UI** with TailwindCSS -- **Form handling** with React Hook Form + +- Interactive **chat with SavageBot** +- **Funny debates** and quote remix features +- Fully **responsive UI** with TailwindCSS +- **Form handling** with React Hook Form - Routing handled via **React Router DOM** --- ## Tech Stack -- **Frontend:** React.js, TypeScript, TailwindCSS -- **Build & Dev Tools:** Vite, ESLint -- **Libraries:** React Hook Form, Lucide React, Tailwind Scrollbar + +- **Frontend:** React.js, TypeScript, TailwindCSS +- **Build & Dev Tools:** Vite, ESLint +- **Libraries:** React Hook Form, Lucide React, Tailwind Scrollbar --- ## Getting Started ### Prerequisites + - Node.js >= 16 - A running instance of the RoastAI backend - `.env` file with the following key: ```env VITE_BACKEND_BASE_URL= -```` +``` ### Installation @@ -55,7 +60,7 @@ npm install npm run dev ``` -* Starts the development server via Vite. +- Starts the development server via Vite. ### Build & Preview diff --git a/eslint.config.js b/eslint.config.js index d94e7de..e821a89 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,9 +1,9 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { globalIgnores } from 'eslint/config' +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; +import { globalIgnores } from 'eslint/config'; export default tseslint.config([ globalIgnores(['dist']), @@ -20,4 +20,4 @@ export default tseslint.config([ globals: globals.browser, }, }, -]) +]); diff --git a/package-lock.json b/package-lock.json index af1df16..1646d8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "prettier": "^3.6.2", "typescript": "~5.8.3", "typescript-eslint": "^8.35.1", "vite": "^7.0.3" @@ -3154,6 +3155,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prism-react-renderer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", diff --git a/package.json b/package.json index e37b93b..2ad2474 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "format": "prettier --write ." }, "dependencies": { "@tailwindcss/vite": "^4.1.11", @@ -30,6 +31,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "prettier": "^3.6.2", "typescript": "~5.8.3", "typescript-eslint": "^8.35.1", "vite": "^7.0.3" diff --git a/src/App.css b/src/App.css index e13b1b8..585bf3b 100644 --- a/src/App.css +++ b/src/App.css @@ -1,26 +1,38 @@ -@import "tailwindcss"; +@import 'tailwindcss'; ::selection { - background-color: black; - color: white; + background-color: black; + color: white; } - :root { --shadow: 50px 0px 200px rgba(175, 167, 167, 0.24); } body { - font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + 'Helvetica Neue', + Arial, + 'Noto Sans', + sans-serif, + 'Apple Color Emoji', + 'Segoe UI Emoji', + 'Segoe UI Symbol', + 'Noto Color Emoji'; } - .custom-scroll::-webkit-scrollbar { - width: 2px; + width: 2px; } .custom-scroll { - scrollbar-width: thin; + scrollbar-width: thin; } .small-scroll::-webkit-scrollbar-thumb { @@ -29,7 +41,6 @@ body { border-radius: 4px; } - @layer base { * { transition-property: all; @@ -38,7 +49,6 @@ body { } } - @layer theme { :root { --app-inner-gutter: 0.75rem; @@ -65,7 +75,7 @@ body { padding-top: var(--app-gutter-xl); padding-bottom: var(--app-gutter-xl); } - + .p-gutter { padding: var(--app-gutter); } @@ -73,13 +83,12 @@ body { .shadow-c { box-shadow: - -1.5px 0px 2px rgba(104, 104, 104, 0.16), /* left */ - 1.5px 0px 2px rgba(104, 104, 104, 0.16), /* right */ - 0px 1.5px 2px rgba(104, 104, 104, 0.16), /* bottom */ - 0px -0.5px 2px rgba(104, 104, 104, 0.1); /* top (reduced) */ + -1.5px 0px 2px rgba(104, 104, 104, 0.16), + /* left */ 1.5px 0px 2px rgba(104, 104, 104, 0.16), + /* right */ 0px 1.5px 2px rgba(104, 104, 104, 0.16), + /* bottom */ 0px -0.5px 2px rgba(104, 104, 104, 0.1); /* top (reduced) */ } - .custom-scrollbar::-webkit-scrollbar { width: 4px; } @@ -91,4 +100,4 @@ body { .custom-scrollbar::-webkit-scrollbar-track { background: transparent; -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 8c56f18..77bdc17 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,11 @@ -import LoginPage from "./page/LoginPage"; -import SignupPage from "./page/SignupPage"; -import HomePage from "./page/HomePage"; -import { Routes, Route } from "react-router-dom"; -import PrivateRoute from "./components/PrivateRoute" -import VerifyEmail from "./components/VerifyEmail"; +import LoginPage from './page/LoginPage'; +import SignupPage from './page/SignupPage'; +import HomePage from './page/HomePage'; +import { Routes, Route } from 'react-router-dom'; +import PrivateRoute from './components/PrivateRoute'; +import VerifyEmail from './components/VerifyEmail'; function App() { - return (
@@ -26,4 +25,4 @@ function App() { ); } -export default App +export default App; diff --git a/src/api/DailyNewsMail.tsx b/src/api/DailyNewsMail.tsx index 936b2cd..641a2c3 100644 --- a/src/api/DailyNewsMail.tsx +++ b/src/api/DailyNewsMail.tsx @@ -1,39 +1,39 @@ -import { useState } from "react"; +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useDailyNewsMail = () => { - const [success, setSuccess] = useState(false); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); - const dailyNewsMailRequest = async (data: any) => { - setSuccess(false); - setError(null); - setLoading(true); + const dailyNewsMailRequest = async (data: any) => { + setSuccess(false); + setError(null); + setLoading(true); - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/newsletter`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - }); + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/newsletter`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(data), + }); - if (!response.ok) { - throw new Error("Internal Server Error"); - } + if (!response.ok) { + throw new Error('Internal Server Error'); + } - setSuccess(true); - } catch (error) { - setError(error instanceof Error ? error.message : "Unknown error"); - console.log((error as Error).message); - } finally { - setLoading(false) - } + setSuccess(true); + } catch (error) { + setError(error instanceof Error ? error.message : 'Unknown error'); + console.log((error as Error).message); + } finally { + setLoading(false); } + }; - return { dailyNewsMailRequest, success, error, loading } -} \ No newline at end of file + return { dailyNewsMailRequest, success, error, loading }; +}; diff --git a/src/api/FunnyCharacters.tsx b/src/api/FunnyCharacters.tsx index 2250567..f43d188 100644 --- a/src/api/FunnyCharacters.tsx +++ b/src/api/FunnyCharacters.tsx @@ -1,46 +1,57 @@ -import { useState } from "react"; +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useFunnyCharacter = () => { - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); - const [funnyCharacterResponse, setFunnyCharacterResponse] = useState(null); - - - const funnyCharacterRequest = async (prompt: string, characterName: string) => { - setError(null); - setSuccess(false); - setLoading(true); - setFunnyCharacterResponse(null); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/funny-characters`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify({ prompt, characterName }) - }) - - if (!response.ok) { - throw new Error("Something Went Wrong") - } - - const data = await response.json(); - const parsedRoast = data?.chatbotReply ? JSON.parse(data.chatbotReply) : null; - - setFunnyCharacterResponse({ ...parsedRoast, prompt: data.prompt }); - setSuccess(true); - } catch (error: any) { - const err = await error?.json() - setError(err instanceof Error ? err.message : "Something Went Wrong"); - } finally { - setLoading(false); - } + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + const [funnyCharacterResponse, setFunnyCharacterResponse] = + useState(null); + + const funnyCharacterRequest = async ( + prompt: string, + characterName: string + ) => { + setError(null); + setSuccess(false); + setLoading(true); + setFunnyCharacterResponse(null); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/funny-characters`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ prompt, characterName }), + }); + + if (!response.ok) { + throw new Error('Something Went Wrong'); + } + + const data = await response.json(); + const parsedRoast = data?.chatbotReply + ? JSON.parse(data.chatbotReply) + : null; + + setFunnyCharacterResponse({ ...parsedRoast, prompt: data.prompt }); + setSuccess(true); + } catch (error: any) { + const err = await error?.json(); + setError(err instanceof Error ? err.message : 'Something Went Wrong'); + } finally { + setLoading(false); } - - return { funnyCharacterRequest, error, success, loading, funnyCharacterResponse }; -} \ No newline at end of file + }; + + return { + funnyCharacterRequest, + error, + success, + loading, + funnyCharacterResponse, + }; +}; diff --git a/src/api/FunnyFeudApi.tsx b/src/api/FunnyFeudApi.tsx index a79f81a..12d056b 100644 --- a/src/api/FunnyFeudApi.tsx +++ b/src/api/FunnyFeudApi.tsx @@ -1,44 +1,54 @@ -import { useState } from "react"; +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useFunnyFeud = () => { - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); - const [funnyFeudResponse, setFunnyFeudResponse] = useState(null); - - const funnyFeudRequest = async ({ AskAnything: prompt, selectedCharacter1, selectedCharacter2 }: any) => { - setError(null); - setSuccess(false); - setLoading(true); - setFunnyFeudResponse(null); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/funny-debate`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - body: JSON.stringify({ prompt, selectedCharacter1, selectedCharacter2 }) - }) - - if (!response.ok) { - throw new Error("Something Went Wrong"); - } - - const data = await response.json(); - const parsedRoast = data?.chatbotReply ? JSON.parse(data.chatbotReply) : null; - - setFunnyFeudResponse({ parsedRoast, prompt: data?.prompt }); - setSuccess(true); - } catch (error) { - setError(error instanceof Error ? error.message : "Something Went Wrong") - } finally { - setLoading(false); - } + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + const [funnyFeudResponse, setFunnyFeudResponse] = useState(null); + + const funnyFeudRequest = async ({ + AskAnything: prompt, + selectedCharacter1, + selectedCharacter2, + }: any) => { + setError(null); + setSuccess(false); + setLoading(true); + setFunnyFeudResponse(null); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/funny-debate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + prompt, + selectedCharacter1, + selectedCharacter2, + }), + }); + + if (!response.ok) { + throw new Error('Something Went Wrong'); + } + + const data = await response.json(); + const parsedRoast = data?.chatbotReply + ? JSON.parse(data.chatbotReply) + : null; + + setFunnyFeudResponse({ parsedRoast, prompt: data?.prompt }); + setSuccess(true); + } catch (error) { + setError(error instanceof Error ? error.message : 'Something Went Wrong'); + } finally { + setLoading(false); } + }; - return { funnyFeudRequest, error, success, loading, funnyFeudResponse }; -} \ No newline at end of file + return { funnyFeudRequest, error, success, loading, funnyFeudResponse }; +}; diff --git a/src/api/FunnyQuotesApi.tsx b/src/api/FunnyQuotesApi.tsx index 5e0feea..15cd3ba 100644 --- a/src/api/FunnyQuotesApi.tsx +++ b/src/api/FunnyQuotesApi.tsx @@ -1,43 +1,45 @@ -import { useState } from "react"; +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useFunnyQuotes = () => { - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); - const [funnyQuotesResponse, setFunnyQuotesResponse] = useState(null); - - const FunnyQuotesRequest = async () => { - setError(null); - setSuccess(false); - setLoading(true); - setFunnyQuotesResponse(null); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/funny-quotes`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - credentials: "include", - }) - - if (!response.ok) { - throw new Error("Something Went Wrong"); - } - - const data = await response.json(); - const parsedfunnyQuotes = data?.chatbotReply ? JSON.parse(data.chatbotReply) : null; - - setFunnyQuotesResponse(parsedfunnyQuotes); - setSuccess(true); - } catch (error) { - setError(error instanceof Error ? error.message : "Something Went Wrong") - } finally { - setLoading(false); - } + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + const [funnyQuotesResponse, setFunnyQuotesResponse] = useState(null); + + const FunnyQuotesRequest = async () => { + setError(null); + setSuccess(false); + setLoading(true); + setFunnyQuotesResponse(null); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/funny-quotes`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('Something Went Wrong'); + } + + const data = await response.json(); + const parsedfunnyQuotes = data?.chatbotReply + ? JSON.parse(data.chatbotReply) + : null; + + setFunnyQuotesResponse(parsedfunnyQuotes); + setSuccess(true); + } catch (error) { + setError(error instanceof Error ? error.message : 'Something Went Wrong'); + } finally { + setLoading(false); } + }; - return { FunnyQuotesRequest, error, success, loading, funnyQuotesResponse }; -} \ No newline at end of file + return { FunnyQuotesRequest, error, success, loading, funnyQuotesResponse }; +}; diff --git a/src/api/MyUserApi.tsx b/src/api/MyUserApi.tsx index 5b4e0fa..d26e100 100644 --- a/src/api/MyUserApi.tsx +++ b/src/api/MyUserApi.tsx @@ -1,147 +1,147 @@ -import { useState } from "react" +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useLoginMyUser = () => { - const [success, setSuccess] = useState(false); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const loginMyUserRequest = async (data: any) => { - setSuccess(false); - setError(null); - setLoading(true); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/users/login`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - credentials: "include", - body: JSON.stringify(data) - }) - - if (!response.ok) { - throw new Error("Failed to login"); - } - - setSuccess(true); - return await response.json() - } catch (error) { - setError(error instanceof Error ? error.message : "Something went wrong"); - } finally { - setLoading(false); - } + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const loginMyUserRequest = async (data: any) => { + setSuccess(false); + setError(null); + setLoading(true); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/users/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error('Failed to login'); + } + + setSuccess(true); + return await response.json(); + } catch (error) { + setError(error instanceof Error ? error.message : 'Something went wrong'); + } finally { + setLoading(false); } + }; - return { success, error, loading, loginMyUserRequest } -} + return { success, error, loading, loginMyUserRequest }; +}; export const useSignupMyUser = () => { - const [success, setSuccess] = useState(false); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - - const signupMyUserRequest = async (data: any) => { - setSuccess(false); - setError(null); - setLoading(true); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/users/signup`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - credentials: "include", - body: JSON.stringify(data) - }) - - if (!response.ok) { - throw new Error("Failed to signup"); - } - - setSuccess(true) - } catch (error) { - setError(error instanceof Error ? error.message : "Something went wrong"); - } finally { - setLoading(false); - } + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const signupMyUserRequest = async (data: any) => { + setSuccess(false); + setError(null); + setLoading(true); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/users/signup`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error('Failed to signup'); + } + + setSuccess(true); + } catch (error) { + setError(error instanceof Error ? error.message : 'Something went wrong'); + } finally { + setLoading(false); } + }; - return { success, error, loading, signupMyUserRequest } -} + return { success, error, loading, signupMyUserRequest }; +}; export const useGetUser = () => { - const [success, setSuccess] = useState(false); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [user, setUser] = useState(null); - - const getUserRequest = async () => { - setSuccess(false); - setError(null); - setLoading(true); - setUser(null); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/users/me`, { - method: "GET", - headers: { - "Content-Type": "application/json" - }, - credentials: "include" - }) - - if (!response.ok) { - throw new Error("Internal server error") - } - - const data = await response.json(); - setUser(data); - setSuccess(true); - return data; - } catch (error) { - setError(error instanceof Error ? error.message : "Something went wrong"); - } finally { - setLoading(false) - } + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + const [user, setUser] = useState(null); + + const getUserRequest = async () => { + setSuccess(false); + setError(null); + setLoading(true); + setUser(null); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/users/me`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('Internal server error'); + } + + const data = await response.json(); + setUser(data); + setSuccess(true); + return data; + } catch (error) { + setError(error instanceof Error ? error.message : 'Something went wrong'); + } finally { + setLoading(false); } + }; - return { getUserRequest, success, error, loading, user } -} + return { getUserRequest, success, error, loading, user }; +}; export const useLogout = () => { - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - - const logoutRequest = async () => { - setLoading(true); - setError(null); - setSuccess(false); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/users/logout`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - credentials: "include", - }); - - if (!response.ok) { - throw new Error("Failed to logout"); - } - - setSuccess(true); - return true; - } catch (err) { - setError(err instanceof Error ? err.message : "Something went wrong"); - return false; - } finally { - setLoading(false); - } - }; - - return { logoutRequest, loading, error, success }; + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + const logoutRequest = async () => { + setLoading(true); + setError(null); + setSuccess(false); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/users/logout`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + }); + + if (!response.ok) { + throw new Error('Failed to logout'); + } + + setSuccess(true); + return true; + } catch (err) { + setError(err instanceof Error ? err.message : 'Something went wrong'); + return false; + } finally { + setLoading(false); + } + }; + + return { logoutRequest, loading, error, success }; }; diff --git a/src/api/RoastMeApi.tsx b/src/api/RoastMeApi.tsx index c548a12..c9761f9 100644 --- a/src/api/RoastMeApi.tsx +++ b/src/api/RoastMeApi.tsx @@ -1,46 +1,45 @@ -import { useState } from "react"; +import { useState } from 'react'; const BACKEND_BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL; export const useRoastMe = () => { - const [error, setError] = useState(null); - const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); - const [roastData, setRoastData] = useState(null) - - const roastMeRequest = async (prompt: any) => { - setError(null); - setSuccess(false); - setLoading(true); - setRoastData(null); - - try { - const response = await fetch(`${BACKEND_BASE_URL}/api/roast`, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - credentials: "include", - body: JSON.stringify({ prompt }) - }) - - if (!response.ok) { - throw new Error("Internal Server Error") - } - - const res = await response.json(); - const parsedRoast = res?.roast ? JSON.parse(res.roast) : null; - - setRoastData({ ...parsedRoast, prompt: res.prompt }); - setSuccess(true); - } catch (error) { - console.log(error); - setError(error instanceof Error ? error.message : "Something went wrong") - } finally { - setLoading(false); - } - + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + const [loading, setLoading] = useState(false); + const [roastData, setRoastData] = useState(null); + + const roastMeRequest = async (prompt: any) => { + setError(null); + setSuccess(false); + setLoading(true); + setRoastData(null); + + try { + const response = await fetch(`${BACKEND_BASE_URL}/api/roast`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ prompt }), + }); + + if (!response.ok) { + throw new Error('Internal Server Error'); + } + + const res = await response.json(); + const parsedRoast = res?.roast ? JSON.parse(res.roast) : null; + + setRoastData({ ...parsedRoast, prompt: res.prompt }); + setSuccess(true); + } catch (error) { + console.log(error); + setError(error instanceof Error ? error.message : 'Something went wrong'); + } finally { + setLoading(false); } + }; - return { roastMeRequest, loading, success, error, roastData }; -} \ No newline at end of file + return { roastMeRequest, loading, success, error, roastData }; +}; diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 95c9653..ea546e6 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,26 +1,26 @@ -import { useMemo } from "react"; -import { getInitials, stringToColor } from "../utils/stringUtils"; +import { useMemo } from 'react'; +import { getInitials, stringToColor } from '../utils/stringUtils'; -export const Avatar = ({ name}: any) => { - const initials = getInitials(name); - const bgColor = useMemo(() => stringToColor(name), [name]); +export const Avatar = ({ name }: any) => { + const initials = getInitials(name); + const bgColor = useMemo(() => stringToColor(name), [name]); - return ( -
- {initials} -
- ); + return ( +
+ {initials} +
+ ); }; diff --git a/src/components/CharacterSuggestInput.tsx b/src/components/CharacterSuggestInput.tsx index d160317..062db50 100644 --- a/src/components/CharacterSuggestInput.tsx +++ b/src/components/CharacterSuggestInput.tsx @@ -1,49 +1,64 @@ -import { ChevronDown } from "lucide-react" -import { useState } from "react" +import { ChevronDown } from 'lucide-react'; +import { useState } from 'react'; -const CharacterSuggestInput = ({ selectedCharacter, setSelectedCharacter }: any) => { - const [open, setOpen] = useState(false) - const [suggestions, setSuggestions] = useState([]); +const CharacterSuggestInput = ({ + selectedCharacter, + setSelectedCharacter, +}: any) => { + const [open, setOpen] = useState(false); + const [suggestions, setSuggestions] = useState([]); - const handleChange = async (e: any) => { - const value = e.target.value; - setSelectedCharacter(value); + const handleChange = async (e: any) => { + const value = e.target.value; + setSelectedCharacter(value); - if (value.trim().length > 0) { - const res = await fetch(`https://api.datamuse.com/sug?s=${value}`); - const data = await res.json(); - setSuggestions(data.map((item: any) => item.word)); - } else { - setSuggestions([]); - } - }; + if (value.trim().length > 0) { + const res = await fetch(`https://api.datamuse.com/sug?s=${value}`); + const data = await res.json(); + setSuggestions(data.map((item: any) => item.word)); + } else { + setSuggestions([]); + } + }; - const handleSelect = (word: any) => { - setSelectedCharacter(word); - setOpen(false); - }; + const handleSelect = (word: any) => { + setSelectedCharacter(word); + setOpen(false); + }; - return ( -
-
setOpen(!open)} className="px-2 shadow-sm rounded-md relative w-full flex items-center border-[0.1px] border-[#a5a5a58a] "> - - 0 ? "" : "rotate-90"}`} - /> -
-
    0 ? "absolute max-h-50 " : "hidden"} overflow-y-auto small-scroll custom-scroll top-10 px-2 bg-white border-[0.1px] border-[#a5a5a58a] rounded-md shadow-md inset-x-0`}> - { selectedCharacter.trim().length > 0 && suggestions.map((suggestion) => ( -
  • handleSelect(suggestion)} className="py-[6px] text-sm my-1 px-2 hover:bg-[#F0F0F0] cursor-pointer active:bg-[#f0f0f09c] hover:rounded-md" key={suggestion}>{suggestion}
  • - ))} -
-
- ) -} + return ( +
+
setOpen(!open)} + className="px-2 shadow-sm rounded-md relative w-full flex items-center border-[0.1px] border-[#a5a5a58a] " + > + + 0 ? '' : 'rotate-90'}`} + /> +
+
    0 ? 'absolute max-h-50 ' : 'hidden'} overflow-y-auto small-scroll custom-scroll top-10 px-2 bg-white border-[0.1px] border-[#a5a5a58a] rounded-md shadow-md inset-x-0`} + > + {selectedCharacter.trim().length > 0 && + suggestions.map((suggestion) => ( +
  • handleSelect(suggestion)} + className="py-[6px] text-sm my-1 px-2 hover:bg-[#F0F0F0] cursor-pointer active:bg-[#f0f0f09c] hover:rounded-md" + key={suggestion} + > + {suggestion} +
  • + ))} +
+
+ ); +}; -export default CharacterSuggestInput \ No newline at end of file +export default CharacterSuggestInput; diff --git a/src/components/EmailVerificationPopup.tsx b/src/components/EmailVerificationPopup.tsx index 4b85c7a..6a0a980 100644 --- a/src/components/EmailVerificationPopup.tsx +++ b/src/components/EmailVerificationPopup.tsx @@ -1,28 +1,28 @@ interface Props { - message: string; - onClose: () => void; + message: string; + onClose: () => void; } const EmailVerificationPopup: React.FC = ({ message, onClose }) => { - return ( -
-
-

- Verify Your Email -

-

{message}

- -
-
- ); + return ( +
+
+

+ Verify Your Email +

+

{message}

+ +
+
+ ); }; export default EmailVerificationPopup; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 858cecb..2b8ba54 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,20 +1,23 @@ -import MenuIcon from "./MenuIcon" -import NewChatIcon from "./NewChatIcon" +import MenuIcon from './MenuIcon'; +import NewChatIcon from './NewChatIcon'; -const Header = ({ setOpenSidebar , openSidebar }:any) => { +const Header = ({ setOpenSidebar, openSidebar }: any) => { return ( -
- setOpenSidebar(!openSidebar)} className="md:hidden flex-shrink-0"> - - - - RoastAI - - - - -
- ) -} +
+ setOpenSidebar(!openSidebar)} + className="md:hidden flex-shrink-0" + > + + + + RoastAI + + + + +
+ ); +}; -export default Header \ No newline at end of file +export default Header; diff --git a/src/components/HoverInfoIcon.tsx b/src/components/HoverInfoIcon.tsx index 9f98de0..217838c 100644 --- a/src/components/HoverInfoIcon.tsx +++ b/src/components/HoverInfoIcon.tsx @@ -1,32 +1,32 @@ -import { useState, useRef } from "react"; +import { useState, useRef } from 'react'; export default function HoverInfoIcon({ icon, label, onClick }: any) { - const [showPopup, setShowPopup] = useState(false); - const iconRef = useRef(null); + const [showPopup, setShowPopup] = useState(false); + const iconRef = useRef(null); - return ( + return ( +
setShowPopup(true)} + onMouseLeave={() => setShowPopup(false)} + onClick={onClick} + className="relative cursor-pointer" + > + {icon} + {showPopup && iconRef.current && (
setShowPopup(true)} - onMouseLeave={() => setShowPopup(false)} - onClick={onClick} - className="relative cursor-pointer" + style={{ + position: 'fixed', + left: iconRef.current.getBoundingClientRect().right + 8 + 'px', + top: iconRef.current.getBoundingClientRect().top + 'px', + }} + className="rounded-md bg-white shadow-sm z-50" > - {icon} - {showPopup && iconRef.current && ( -
-

- {label} -

-
- )} +

+ {label} +

- ); + )} +
+ ); } diff --git a/src/components/MainContent.tsx b/src/components/MainContent.tsx index e30eb7f..074ede3 100644 --- a/src/components/MainContent.tsx +++ b/src/components/MainContent.tsx @@ -1,55 +1,76 @@ -import Header from "./Header"; -import RoastDisplay from "./RoastDisplay"; +import Header from './Header'; +import RoastDisplay from './RoastDisplay'; -const MainContent = ({ setOpenSidebar, openSidebar, roastData, loading, handleSubmit, onSubmit, register, handleInputChange, floatingSendButton }: any) => { - return ( - <> - {/* Header */} -
- {/* Main Page */} -
- {/* Response Section */} -
- -
- {/* Ask anything */} -
-
-