Skip to content

Commit

Permalink
3rd part completed - connecting frontend and backend
Browse files Browse the repository at this point in the history
burakorkmez committed Jan 30, 2024
1 parent 7aadf4f commit cecf505
Showing 28 changed files with 734 additions and 102 deletions.
36 changes: 17 additions & 19 deletions frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
settings: { react: { version: "18.2" } },
plugins: ["react-refresh"],
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"react/prop-types": "off",
},
};
110 changes: 104 additions & 6 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -12,7 +12,10 @@
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.0.1"
"react-hot-toast": "^2.4.1",
"react-icons": "^5.0.1",
"react-router-dom": "^6.21.3",
"zustand": "^4.5.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
13 changes: 12 additions & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import { Navigate, Route, Routes } from "react-router-dom";
import "./App.css";
import Home from "./pages/home/Home";
import Login from "./pages/login/Login";
import SignUp from "./pages/signup/SignUp";
import { Toaster } from "react-hot-toast";
import { useAuthContext } from "./context/AuthContext";

function App() {
const { authUser } = useAuthContext();
return (
<div className='p-4 h-screen flex items-center justify-center'>
<Home />
<Routes>
<Route path='/' element={authUser ? <Home /> : <Navigate to={"/login"} />} />
<Route path='/login' element={authUser ? <Navigate to='/' /> : <Login />} />
<Route path='/signup' element={authUser ? <Navigate to='/' /> : <SignUp />} />
</Routes>
<Toaster />
</div>
);
}
27 changes: 17 additions & 10 deletions frontend/src/components/messages/Message.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
const Message = () => {
import { useAuthContext } from "../../context/AuthContext";
import { extractTime } from "../../utils/extractTime";
import useConversation from "../../zustand/useConversation";

const Message = ({ message }) => {
const { authUser } = useAuthContext();
const { selectedConversation } = useConversation();
const fromMe = message.senderId === authUser._id;
const formattedTime = extractTime(message.createdAt);
const chatClassName = fromMe ? "chat-end" : "chat-start";
const profilePic = fromMe ? authUser.profilePic : selectedConversation?.profilePic;
const bubbleBgColor = fromMe ? "bg-blue-500" : "";

return (
<div className='chat chat-end'>
<div className={`chat ${chatClassName}`}>
<div className='chat-image avatar'>
<div className='w-10 rounded-full'>
<img
alt='Tailwind CSS chat bubble component'
src={
"https://cdn0.iconfinder.com/data/icons/communication-line-10/24/account_profile_user_contact_person_avatar_placeholder-512.png"
}
/>
<img alt='Tailwind CSS chat bubble component' src={profilePic} />
</div>
</div>
<div className={`chat-bubble text-white bg-blue-500`}>Hi! What is upp?</div>
<div className='chat-footer opacity-50 text-xs flex gap-1 items-center'>12:42</div>
<div className={`chat-bubble text-white ${bubbleBgColor} pb-2`}>{message.message}</div>
<div className='chat-footer opacity-50 text-xs flex gap-1 items-center'>{formattedTime}</div>
</div>
);
};
14 changes: 11 additions & 3 deletions frontend/src/components/messages/MessageContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { useEffect } from "react";
import useConversation from "../../zustand/useConversation";
import MessageInput from "./MessageInput";
import Messages from "./Messages";
import { TiMessages } from "react-icons/ti";

const MessageContainer = () => {
const noChatSelected = true;
const { selectedConversation, setSelectedConversation } = useConversation();

useEffect(() => {
// cleanup function (unmounts)
return () => setSelectedConversation(null);
}, [setSelectedConversation]);

return (
<div className='md:min-w-[450px] flex flex-col'>
{noChatSelected ? (
{!selectedConversation ? (
<NoChatSelected />
) : (
<>
{/* Header */}
<div className='bg-slate-500 px-4 py-2 mb-2'>
<span className='label-text'>To:</span>{" "}
<span className='text-gray-900 font-bold'>John doe</span>
<span className='text-gray-900 font-bold'>{selectedConversation.fullName}</span>
</div>
<Messages />
<MessageInput />
18 changes: 16 additions & 2 deletions frontend/src/components/messages/MessageInput.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import { useState } from "react";
import { BsSend } from "react-icons/bs";
import useSendMessage from "../../hooks/useSendMessage";

const MessageInput = () => {
const [message, setMessage] = useState("");
const { loading, sendMessage } = useSendMessage();

const handleSubmit = async (e) => {
e.preventDefault();
if (!message) return;
await sendMessage(message);
setMessage("");
};

return (
<form className='px-4 my-3'>
<form className='px-4 my-3' onSubmit={handleSubmit}>
<div className='w-full relative'>
<input
type='text'
className='border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 text-white'
placeholder='Send a message'
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type='submit' className='absolute inset-y-0 end-0 flex items-center pe-3'>
<BsSend />
{loading ? <div className='loading loading-spinner'></div> : <BsSend />}
</button>
</div>
</form>
36 changes: 24 additions & 12 deletions frontend/src/components/messages/Messages.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { useEffect, useRef } from "react";
import useGetMessages from "../../hooks/useGetMessages";
import MessageSkeleton from "../skeletons/MessageSkeleton";
import Message from "./Message";

const Messages = () => {
const { messages, loading } = useGetMessages();
const lastMessageRef = useRef();

useEffect(() => {
setTimeout(() => {
lastMessageRef.current?.scrollIntoView({ behavior: "smooth" });
}, 100);
}, [messages]);

return (
<div className='px-4 flex-1 overflow-auto'>
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
<Message />
{!loading &&
messages.length > 0 &&
messages.map((message) => (
<div key={message._id} ref={lastMessageRef}>
<Message message={message} />
</div>
))}

{loading && [...Array(3)].map((_, idx) => <MessageSkeleton key={idx} />)}
{!loading && messages.length === 0 && (
<p className='text-center'>Send a message to start the conversation</p>
)}
</div>
);
};
26 changes: 17 additions & 9 deletions frontend/src/components/sidebar/Conversation.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
const Conversation = () => {
import useConversation from "../../zustand/useConversation";

const Conversation = ({ conversation, lastIdx, emoji }) => {
const { selectedConversation, setSelectedConversation } = useConversation();

const isSelected = selectedConversation?._id === conversation._id;

return (
<>
<div className='flex gap-2 items-center hover:bg-sky-500 rounded p-2 py-1 cursor-pointer'>
<div
className={`flex gap-2 items-center hover:bg-sky-500 rounded p-2 py-1 cursor-pointer
${isSelected ? "bg-sky-500" : ""}
`}
onClick={() => setSelectedConversation(conversation)}
>
<div className='avatar online'>
<div className='w-12 rounded-full'>
<img
src='https://cdn0.iconfinder.com/data/icons/communication-line-10/24/account_profile_user_contact_person_avatar_placeholder-512.png'
alt='user avatar'
/>
<img src={conversation.profilePic} alt='user avatar' />
</div>
</div>

<div className='flex flex-col flex-1'>
<div className='flex gap-3 justify-between'>
<p className='font-bold text-gray-200'>John Doe</p>
<span className='text-xl'>🎃</span>
<p className='font-bold text-gray-200'>{conversation.fullName}</p>
<span className='text-xl'>{emoji}</span>
</div>
</div>
</div>

<div className='divider my-0 py-0 h-1' />
{!lastIdx && <div className='divider my-0 py-0 h-1' />}
</>
);
};
19 changes: 13 additions & 6 deletions frontend/src/components/sidebar/Conversations.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import useGetConversations from "../../hooks/useGetConversations";
import { getRandomEmoji } from "../../utils/emojis";
import Conversation from "./Conversation";

const Conversations = () => {
const { loading, conversations } = useGetConversations();
return (
<div className='py-2 flex flex-col overflow-auto'>
<Conversation />
<Conversation />
<Conversation />
<Conversation />
<Conversation />
<Conversation />
{conversations.map((conversation, idx) => (
<Conversation
key={conversation._id}
conversation={conversation}
emoji={getRandomEmoji()}
lastIdx={idx === conversations.length - 1}
/>
))}

{loading ? <span className='loading loading-spinner mx-auto'></span> : null}
</div>
);
};
10 changes: 9 additions & 1 deletion frontend/src/components/sidebar/LogoutButton.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { BiLogOut } from "react-icons/bi";
import useLogout from "../../hooks/useLogout";

const LogoutButton = () => {
const { loading, logout } = useLogout();

return (
<div className='mt-auto'>
<BiLogOut className='w-6 h-6 text-white cursor-pointer' />
{!loading ? (
<BiLogOut className='w-6 h-6 text-white cursor-pointer' onClick={logout} />
) : (
<span className='loading loading-spinner'></span>
)}
</div>
);
};
32 changes: 30 additions & 2 deletions frontend/src/components/sidebar/SearchInput.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import { useState } from "react";
import { IoSearchSharp } from "react-icons/io5";
import useConversation from "../../zustand/useConversation";
import useGetConversations from "../../hooks/useGetConversations";
import toast from "react-hot-toast";

const SearchInput = () => {
const [search, setSearch] = useState("");
const { setSelectedConversation } = useConversation();
const { conversations } = useGetConversations();

const handleSubmit = (e) => {
e.preventDefault();
if (!search) return;
if (search.length < 3) {
return toast.error("Search term must be at least 3 characters long");
}

const conversation = conversations.find((c) => c.fullName.toLowerCase().includes(search.toLowerCase()));

if (conversation) {
setSelectedConversation(conversation);
setSearch("");
} else toast.error("No such user found!");
};
return (
<form className='flex items-center gap-2'>
<input type='text' placeholder='Search…' className='input input-bordered rounded-full' />
<form onSubmit={handleSubmit} className='flex items-center gap-2'>
<input
type='text'
placeholder='Search…'
className='input input-bordered rounded-full'
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<button type='submit' className='btn btn-circle bg-sky-500 text-white'>
<IoSearchSharp className='w-6 h-6 outline-none' />
</button>
20 changes: 20 additions & 0 deletions frontend/src/components/skeletons/MessageSkeleton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const MessageSkeleton = () => {
return (
<>
<div className='flex gap-3 items-center'>
<div className='skeleton w-10 h-10 rounded-full shrink-0'></div>
<div className='flex flex-col gap-1'>
<div className='skeleton h-4 w-40'></div>
<div className='skeleton h-4 w-40'></div>
</div>
</div>
<div className='flex gap-3 items-center justify-end'>
<div className='flex flex-col gap-1'>
<div className='skeleton h-4 w-40'></div>
</div>
<div className='skeleton w-10 h-10 rounded-full shrink-0'></div>
</div>
</>
);
};
export default MessageSkeleton;
14 changes: 14 additions & 0 deletions frontend/src/context/AuthContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext, useContext, useState } from "react";

export const AuthContext = createContext();

// eslint-disable-next-line react-refresh/only-export-components
export const useAuthContext = () => {
return useContext(AuthContext);
};

export const AuthContextProvider = ({ children }) => {
const [authUser, setAuthUser] = useState(JSON.parse(localStorage.getItem("chat-user")) || null);

return <AuthContext.Provider value={{ authUser, setAuthUser }}>{children}</AuthContext.Provider>;
};
30 changes: 30 additions & 0 deletions frontend/src/hooks/useGetConversations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useEffect, useState } from "react";
import toast from "react-hot-toast";

const useGetConversations = () => {
const [loading, setLoading] = useState(false);
const [conversations, setConversations] = useState([]);

useEffect(() => {
const getConversations = async () => {
setLoading(true);
try {
const res = await fetch("/api/users");
const data = await res.json();
if (data.error) {
throw new Error(data.error);
}
setConversations(data);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

getConversations();
}, []);

return { loading, conversations };
};
export default useGetConversations;
29 changes: 29 additions & 0 deletions frontend/src/hooks/useGetMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useEffect, useState } from "react";
import useConversation from "../zustand/useConversation";
import toast from "react-hot-toast";

const useGetMessages = () => {
const [loading, setLoading] = useState(false);
const { messages, setMessages, selectedConversation } = useConversation();

useEffect(() => {
const getMessages = async () => {
setLoading(true);
try {
const res = await fetch(`/api/messages/${selectedConversation._id}`);
const data = await res.json();
if (data.error) throw new Error(data.error);
setMessages(data);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

if (selectedConversation?._id) getMessages();
}, [selectedConversation?._id, setMessages]);

return { messages, loading };
};
export default useGetMessages;
45 changes: 45 additions & 0 deletions frontend/src/hooks/useLogin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from "react";
import toast from "react-hot-toast";
import { useAuthContext } from "../context/AuthContext";

const useLogin = () => {
const [loading, setLoading] = useState(false);
const { setAuthUser } = useAuthContext();

const login = async (username, password) => {
const success = handleInputErrors(username, password);
if (!success) return;
setLoading(true);
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});

const data = await res.json();
if (data.error) {
throw new Error(data.error);
}

localStorage.setItem("chat-user", JSON.stringify(data));
setAuthUser(data);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

return { loading, login };
};
export default useLogin;

function handleInputErrors(username, password) {
if (!username || !password) {
toast.error("Please fill in all fields");
return false;
}

return true;
}
32 changes: 32 additions & 0 deletions frontend/src/hooks/useLogout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from "react";
import { useAuthContext } from "../context/AuthContext";
import toast from "react-hot-toast";

const useLogout = () => {
const [loading, setLoading] = useState(false);
const { setAuthUser } = useAuthContext();

const logout = async () => {
setLoading(true);
try {
const res = await fetch("/api/auth/logout", {
method: "POST",
headers: { "Content-Type": "application/json" },
});
const data = await res.json();
if (data.error) {
throw new Error(data.error);
}

localStorage.removeItem("chat-user");
setAuthUser(null);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

return { loading, logout };
};
export default useLogout;
32 changes: 32 additions & 0 deletions frontend/src/hooks/useSendMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useState } from "react";
import useConversation from "../zustand/useConversation";
import toast from "react-hot-toast";

const useSendMessage = () => {
const [loading, setLoading] = useState(false);
const { messages, setMessages, selectedConversation } = useConversation();

const sendMessage = async (message) => {
setLoading(true);
try {
const res = await fetch(`/api/messages/send/${selectedConversation._id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ message }),
});
const data = await res.json();
if (data.error) throw new Error(data.error);

setMessages([...messages, data]);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

return { sendMessage, loading };
};
export default useSendMessage;
55 changes: 55 additions & 0 deletions frontend/src/hooks/useSignup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from "react";
import toast from "react-hot-toast";
import { useAuthContext } from "../context/AuthContext";

const useSignup = () => {
const [loading, setLoading] = useState(false);
const { setAuthUser } = useAuthContext();

const signup = async ({ fullName, username, password, confirmPassword, gender }) => {
const success = handleInputErrors({ fullName, username, password, confirmPassword, gender });
if (!success) return;

setLoading(true);
try {
const res = await fetch("/api/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ fullName, username, password, confirmPassword, gender }),
});

const data = await res.json();
if (data.error) {
throw new Error(data.error);
}
localStorage.setItem("chat-user", JSON.stringify(data));
setAuthUser(data);
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};

return { loading, signup };
};
export default useSignup;

function handleInputErrors({ fullName, username, password, confirmPassword, gender }) {
if (!fullName || !username || !password || !confirmPassword || !gender) {
toast.error("Please fill in all fields");
return false;
}

if (password !== confirmPassword) {
toast.error("Passwords do not match");
return false;
}

if (password.length < 6) {
toast.error("Password must be at least 6 characters");
return false;
}

return true;
}
24 changes: 15 additions & 9 deletions frontend/src/main.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import { AuthContextProvider } from "./context/AuthContext.jsx";

ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<AuthContextProvider>
<App />
</AuthContextProvider>
</BrowserRouter>
</React.StrictMode>
);
34 changes: 29 additions & 5 deletions frontend/src/pages/login/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { useState } from "react";
import { Link } from "react-router-dom";
import useLogin from "../../hooks/useLogin";

const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");

const { loading, login } = useLogin();

const handleSubmit = async (e) => {
e.preventDefault();
await login(username, password);
};

return (
<div className='flex flex-col items-center justify-center min-w-96 mx-auto'>
<div className='w-full p-6 rounded-lg shadow-md bg-gray-400 bg-clip-padding backdrop-filter backdrop-blur-lg bg-opacity-0'>
@@ -7,12 +21,18 @@ const Login = () => {
<span className='text-blue-500'> ChatApp</span>
</h1>

<form>
<form onSubmit={handleSubmit}>
<div>
<label className='label p-2'>
<span className='text-base label-text'>Username</span>
</label>
<input type='text' placeholder='Enter username' className='w-full input input-bordered h-10' />
<input
type='text'
placeholder='Enter username'
className='w-full input input-bordered h-10'
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>

<div>
@@ -23,14 +43,18 @@ const Login = () => {
type='password'
placeholder='Enter Password'
className='w-full input input-bordered h-10'
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<a href='#' className='text-sm hover:underline hover:text-blue-600 mt-2 inline-block'>
<Link to='/signup' className='text-sm hover:underline hover:text-blue-600 mt-2 inline-block'>
{"Don't"} have an account?
</a>
</Link>

<div>
<button className='btn btn-block btn-sm mt-2'>Login</button>
<button className='btn btn-block btn-sm mt-2' disabled={loading}>
{loading ? <span className='loading loading-spinner '></span> : "Login"}
</button>
</div>
</form>
</div>
20 changes: 15 additions & 5 deletions frontend/src/pages/signup/GenderCheckbox.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
const GenderCheckbox = () => {
const GenderCheckbox = ({ onCheckboxChange, selectedGender }) => {
return (
<div className='flex'>
<div className='form-control'>
<label className={`label gap-2 cursor-pointer`}>
<label className={`label gap-2 cursor-pointer ${selectedGender === "male" ? "selected" : ""} `}>
<span className='label-text'>Male</span>
<input type='checkbox' className='checkbox border-slate-900' />
<input
type='checkbox'
className='checkbox border-slate-900'
checked={selectedGender === "male"}
onChange={() => onCheckboxChange("male")}
/>
</label>
</div>
<div className='form-control'>
<label className={`label gap-2 cursor-pointer`}>
<label className={`label gap-2 cursor-pointer ${selectedGender === "female" ? "selected" : ""}`}>
<span className='label-text'>Female</span>
<input type='checkbox' className='checkbox border-slate-900' />
<input
type='checkbox'
className='checkbox border-slate-900'
checked={selectedGender === "female"}
onChange={() => onCheckboxChange("female")}
/>
</label>
</div>
</div>
58 changes: 51 additions & 7 deletions frontend/src/pages/signup/SignUp.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
import { Link } from "react-router-dom";
import GenderCheckbox from "./GenderCheckbox";
import { useState } from "react";
import useSignup from "../../hooks/useSignup";

const SignUp = () => {
const [inputs, setInputs] = useState({
fullName: "",
username: "",
password: "",
confirmPassword: "",
gender: "",
});

const { loading, signup } = useSignup();

const handleCheckboxChange = (gender) => {
setInputs({ ...inputs, gender });
};

const handleSubmit = async (e) => {
e.preventDefault();
await signup(inputs);
};

return (
<div className='flex flex-col items-center justify-center min-w-96 mx-auto'>
<div className='w-full p-6 rounded-lg shadow-md bg-gray-400 bg-clip-padding backdrop-filter backdrop-blur-lg bg-opacity-0'>
<h1 className='text-3xl font-semibold text-center text-gray-300'>
Sign Up <span className='text-blue-500'> ChatApp</span>
</h1>

<form>
<form onSubmit={handleSubmit}>
<div>
<label className='label p-2'>
<span className='text-base label-text'>Full Name</span>
</label>
<input type='text' placeholder='John Doe' className='w-full input input-bordered h-10' />
<input
type='text'
placeholder='John Doe'
className='w-full input input-bordered h-10'
value={inputs.fullName}
onChange={(e) => setInputs({ ...inputs, fullName: e.target.value })}
/>
</div>

<div>
<label className='label p-2 '>
<span className='text-base label-text'>Username</span>
</label>
<input type='text' placeholder='johndoe' className='w-full input input-bordered h-10' />
<input
type='text'
placeholder='johndoe'
className='w-full input input-bordered h-10'
value={inputs.username}
onChange={(e) => setInputs({ ...inputs, username: e.target.value })}
/>
</div>

<div>
@@ -31,6 +65,8 @@ const SignUp = () => {
type='password'
placeholder='Enter Password'
className='w-full input input-bordered h-10'
value={inputs.password}
onChange={(e) => setInputs({ ...inputs, password: e.target.value })}
/>
</div>

@@ -42,17 +78,25 @@ const SignUp = () => {
type='password'
placeholder='Confirm Password'
className='w-full input input-bordered h-10'
value={inputs.confirmPassword}
onChange={(e) => setInputs({ ...inputs, confirmPassword: e.target.value })}
/>
</div>

<GenderCheckbox />
<GenderCheckbox onCheckboxChange={handleCheckboxChange} selectedGender={inputs.gender} />

<a className='text-sm hover:underline hover:text-blue-600 mt-2 inline-block' href='#'>
<Link
to={"/login"}
className='text-sm hover:underline hover:text-blue-600 mt-2 inline-block'
href='#'
>
Already have an account?
</a>
</Link>

<div>
<button className='btn btn-block btn-sm mt-2 border border-slate-700'>Sign Up</button>
<button className='btn btn-block btn-sm mt-2 border border-slate-700' disabled={loading}>
{loading ? <span className='loading loading-spinner'></span> : "Sign Up"}
</button>
</div>
</form>
</div>
60 changes: 60 additions & 0 deletions frontend/src/utils/emojis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export const funEmojis = [
"👾",
"⭐",
"🌟",
"🎉",
"🎊",
"🎈",
"🎁",
"🎂",
"🎄",
"🎃",
"🎗",
"🎟",
"🎫",
"🎖",
"🏆",
"🏅",
"🥇",
"🥈",
"🥉",
"⚽",
"🏀",
"🏈",
"⚾",
"🎾",
"🏐",
"🏉",
"🎱",
"🏓",
"🏸",
"🥅",
"🏒",
"🏑",
"🏏",
"⛳",
"🏹",
"🎣",
"🥊",
"🥋",
"🎽",
"⛸",
"🥌",
"🛷",
"🎿",
"⛷",
"🏂",
"🏋️",
"🤼",
"🤸",
"🤺",
"⛹️",
"🤾",
"🏌️",
"🏇",
"🧘",
];

export const getRandomEmoji = () => {
return funEmojis[Math.floor(Math.random() * funEmojis.length)];
};
11 changes: 11 additions & 0 deletions frontend/src/utils/extractTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function extractTime(dateString) {
const date = new Date(dateString);
const hours = padZero(date.getHours());
const minutes = padZero(date.getMinutes());
return `${hours}:${minutes}`;
}

// Helper function to pad single-digit numbers with a leading zero
function padZero(number) {
return number.toString().padStart(2, "0");
}
10 changes: 10 additions & 0 deletions frontend/src/zustand/useConversation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { create } from "zustand";

const useConversation = create((set) => ({
selectedConversation: null,
setSelectedConversation: (selectedConversation) => set({ selectedConversation }),
messages: [],
setMessages: (messages) => set({ messages }),
}));

export default useConversation;
16 changes: 12 additions & 4 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
plugins: [react()],
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:5000",
},
},
},
});

0 comments on commit cecf505

Please sign in to comment.