From 0ac655b83a485b3070d32ad84c5edf9cc95050ae Mon Sep 17 00:00:00 2001 From: slawk0 Date: Thu, 2 Jan 2025 20:10:08 +0100 Subject: [PATCH] code refactor --- client/src/App.tsx | 59 ++++++------------- .../chat/chatArea/AnimatedMessage.tsx | 22 +++---- .../components/chat/chatArea/MessageForm.tsx | 3 +- .../components/chat/chatArea/MessagesArea.tsx | 13 +--- .../chat/leftSidebar/UserProfile.tsx | 7 +-- .../chat/rightSidebar/ParticipantsBar.tsx | 28 ++++----- client/src/components/ui/sidebar.tsx | 2 +- client/src/pages/Login.tsx | 2 +- client/src/pages/Signup.tsx | 2 +- client/src/types/types.ts | 4 +- client/src/utils/AuthContext.tsx | 16 +++++ client/src/utils/AuthProvider.tsx | 57 ++++++++++++++---- client/src/utils/ProtectedRoutes.tsx | 27 ++------- client/src/utils/PublicRoute.tsx | 7 +-- client/src/utils/useAuth.tsx | 35 +---------- docker-compose.yml | 2 + 16 files changed, 130 insertions(+), 156 deletions(-) create mode 100644 client/src/utils/AuthContext.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index a06f1f1..3037371 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -3,18 +3,17 @@ import { Navigate, RouterProvider, } from 'react-router-dom'; -import { useEffect, useState } from 'react'; -import Chat from './pages/Chat.tsx'; -import Login from './pages/Login.tsx'; -import Signup from './pages/Signup.tsx'; -import Settings from './pages/Settings.tsx'; -import Lost from './pages/404.tsx'; -import { AuthContext } from './utils/AuthProvider.tsx'; -import ProtectedRoutes from './utils/ProtectedRoutes.tsx'; -import PublicRoute from '@/utils/PublicRoute.tsx'; -import Cookies from 'js-cookie'; -import { ChatProvider } from '@/context/chat/ChatProvider.tsx'; -import { axiosClient } from '@/utils/axiosClient.ts'; +import { Suspense } from 'react'; +import Chat from './pages/Chat'; +import Login from './pages/Login'; +import Signup from './pages/Signup'; +import Settings from './pages/Settings'; +import Lost from './pages/404'; +import { AuthProvider } from '@/utils/AuthProvider.tsx'; +import ProtectedRoutes from './utils/ProtectedRoutes'; +import PublicRoute from './utils/PublicRoute'; +import { ChatProvider } from './context/chat/ChatProvider'; +import LoadingScreen from './components/LoadingScreen'; const router = createBrowserRouter([ { @@ -27,9 +26,11 @@ const router = createBrowserRouter([ { path: '/chat', element: ( - - - + }> + + + + ), }, { @@ -58,34 +59,10 @@ const router = createBrowserRouter([ ]); function App() { - const hasToken = Boolean(Cookies.get('token')); - const [authorized, setAuthorized] = useState(false); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - async function validateToken() { - if (!hasToken) return; - - try { - await axiosClient.get('/api/auth/validate', { - withCredentials: true, - }); - setAuthorized(true); - } catch (e) { - setAuthorized(false); - console.log('Failed to validate token: ', e); - } finally { - setIsLoading(false); - } - } - - validateToken(); - }, [hasToken]); - return ( - + - + ); } diff --git a/client/src/components/chat/chatArea/AnimatedMessage.tsx b/client/src/components/chat/chatArea/AnimatedMessage.tsx index bc9956b..ae21ef6 100644 --- a/client/src/components/chat/chatArea/AnimatedMessage.tsx +++ b/client/src/components/chat/chatArea/AnimatedMessage.tsx @@ -1,22 +1,18 @@ import { useState } from 'react'; import { Trash2 } from 'lucide-react'; import AttachmentPreview from './AttachmentPreview.tsx'; - -import { ChatMessagesProps } from '@/types/types.ts'; +import { ChatMessagesProps, UserType } from '@/types/types.ts'; +import { useOutletContext } from 'react-router-dom'; +import { useChat } from '@/context/chat/useChat.ts'; type AnimatedMessageProps = { message: ChatMessagesProps; - isAdmin: boolean; - currentUsername: string | null; onDelete: (messageId: number) => void; }; -const AnimatedMessage = ({ - message, - isAdmin, - currentUsername, - onDelete, -}: AnimatedMessageProps) => { +const AnimatedMessage = ({ onDelete, message }: AnimatedMessageProps) => { + const user: UserType = useOutletContext(); + const { me, groupOwner } = useChat(); const [isRemoving, setIsRemoving] = useState(false); const handleDelete = () => { @@ -54,7 +50,11 @@ const AnimatedMessage = ({ )} - {message.sender === currentUsername || isAdmin ? ( + {me.isGroupOwner || + message.sender == user.username || + (me.isGroupAdmin && + me.isGroupOwner && + message.sender_id !== groupOwner) ? ( { }; const submitMessage: SubmitHandler = async (data) => { - if ((!data.message && files.length === 0) || isSending) return; + if ((data.message.trim().length < 1 && files.length === 0) || isSending) + return; setErrorMessage(null); setIsSending(true); if (!socket) { diff --git a/client/src/components/chat/chatArea/MessagesArea.tsx b/client/src/components/chat/chatArea/MessagesArea.tsx index e015562..4b62ab0 100644 --- a/client/src/components/chat/chatArea/MessagesArea.tsx +++ b/client/src/components/chat/chatArea/MessagesArea.tsx @@ -4,7 +4,7 @@ import { useOutletContext } from 'react-router-dom'; import { sendContact } from '@/api/contactsApi.tsx'; import LoadingWheel from '../LoadingWheel.tsx'; import AnimatedMessage from '@/components/chat/chatArea/AnimatedMessage.tsx'; -import { ChatMessagesProps, UsernameType } from '@/types/types.ts'; +import { ChatMessagesProps, UserType } from '@/types/types.ts'; import { useChat } from '@/context/chat/useChat.ts'; function MessagesArea() { @@ -17,10 +17,9 @@ function MessagesArea() { setContactsList, errorMessage, fetchPreviousMessages, - me, } = useChat(); const containerRef = useRef(null); - const user: UsernameType = useOutletContext(); + const user: UserType = useOutletContext(); const [isLoading, setIsLoading] = useState(false); const [shouldScrollToBottom, setShouldScrollToBottom] = useState(true); const previousMessagesLength = useRef(messages.length); @@ -223,13 +222,7 @@ function MessagesArea() {
    {isLoading ? : null} {messages.map((msg: ChatMessagesProps) => ( - + ))}
diff --git a/client/src/components/chat/leftSidebar/UserProfile.tsx b/client/src/components/chat/leftSidebar/UserProfile.tsx index 7b1692a..9ca1b4f 100644 --- a/client/src/components/chat/leftSidebar/UserProfile.tsx +++ b/client/src/components/chat/leftSidebar/UserProfile.tsx @@ -2,17 +2,16 @@ import zdjecie from '../../../../assets/turtleProfileImg3.webp'; import logoutIcon from '../../../../assets/logout.svg'; import Cookies from 'js-cookie'; import { useOutletContext } from 'react-router-dom'; - -import { UsernameType } from '@/types/types.ts'; +import { UserType } from '@/types/types.ts'; function UserProfile() { - const user: UsernameType = useOutletContext(); - + const user: UserType = useOutletContext(); function logout() { Cookies.remove('token'); localStorage.removeItem('contact'); window.location.reload(); } + return (
([]); - const user: UsernameType = useOutletContext(); + const user: UserType = useOutletContext(); const getParticipants = async () => { try { const response = await axiosClient.get( @@ -90,22 +90,23 @@ function ParticipantsBar() { }; useEffect(() => { - if (participants.length > 0 && user?.user_id) { + console.error(participants.length, user.id); + if (participants.length > 0 && user?.id) { const userIsAdmin = participants.some( - (participant) => - participant.user_id === user.user_id && participant.isadmin, + (participant) => participant.user_id === user.id && participant.isadmin, ); const userIsOwner = participants.some( - (participant) => - participant.user_id === user.user_id && participant.isowner, + (participant) => participant.user_id === user.id && participant.isowner, ); + const whoIsOwner = participants.find( (participant) => participant.isowner, ); setGroupOwner(whoIsOwner?.user_id); setMe({ isGroupAdmin: userIsAdmin, isGroupOwner: userIsOwner }); + console.error('SETME: ', userIsAdmin, userIsOwner); } - }, [participants, user?.user_id]); + }, [participants, user?.id]); useEffect(() => { if (currentContact) { @@ -140,7 +141,7 @@ function ParticipantsBar() { const { group_id } = msg; if ( msg.group_id == currentContact?.conversation_id && - msg.user_id == user?.user_id + msg.user_id == user?.id ) { initializeContact({ read: true, @@ -172,7 +173,7 @@ function ParticipantsBar() { const handleLeftGroup = (msg: { user_id: string; group_id: string }) => { if ( msg.group_id == currentContact?.conversation_id && - msg.user_id == user?.user_id + msg.user_id == user?.id ) { setParticipants([]); initializeContact(currentContact); @@ -200,7 +201,7 @@ function ParticipantsBar() { }) => { console.log('(socket) removed administrator: ', msg); if (msg.group_id === currentContact.conversation_id) { - if (msg.user_id === user?.user_id) { + if (msg.user_id === user?.id) { setMe({ isGroupAdmin: false, isGroupOwner: false }); } setParticipants((prevMembers) => @@ -222,7 +223,7 @@ function ParticipantsBar() { socket?.off('added to group'); socket?.off('left group'); }; - }, [socket, currentContact, currentContact, user?.user_id]); + }, [socket, currentContact, currentContact, user?.id]); const ParticipantsList = sortedParticipants?.map( (participant: ParticipantsProps) => ( @@ -255,7 +256,7 @@ function ParticipantsBar() { - {user.user_id !== participant.user_id && + {user.id !== participant.user_id && me.isGroupAdmin && (!participant.isadmin || me.isGroupOwner) ? ( @@ -298,7 +299,6 @@ function ParticipantsBar() {

Members

-
    {ParticipantsList}
); diff --git a/client/src/components/ui/sidebar.tsx b/client/src/components/ui/sidebar.tsx index c1091cb..95d8426 100644 --- a/client/src/components/ui/sidebar.tsx +++ b/client/src/components/ui/sidebar.tsx @@ -93,7 +93,7 @@ const SidebarProvider = React.forwardRef< : setOpen((open) => !open); }, [isMobile, setOpen, setOpenMobile]); - useEffect(() => { + React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ( event.key === SIDEBAR_KEYBOARD_SHORTCUT && diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx index bf0ee8b..82a40c6 100644 --- a/client/src/pages/Login.tsx +++ b/client/src/pages/Login.tsx @@ -2,7 +2,7 @@ import { useForm, SubmitHandler } from 'react-hook-form'; import icon from '../../assets/icon.png'; import { Link, useNavigate } from 'react-router-dom'; import { useContext, useState } from 'react'; -import { AuthContext } from '../utils/AuthProvider.tsx'; +import { AuthContext } from '@/utils/AuthContext.tsx'; import LoadingWheel from '../components/chat/LoadingWheel.tsx'; import { axiosClient } from '@/utils/axiosClient.ts'; diff --git a/client/src/pages/Signup.tsx b/client/src/pages/Signup.tsx index 892cd9a..e545a94 100644 --- a/client/src/pages/Signup.tsx +++ b/client/src/pages/Signup.tsx @@ -2,7 +2,7 @@ import icon from '../../assets/icon.png'; import { useForm, SubmitHandler } from 'react-hook-form'; import { Link, useNavigate } from 'react-router-dom'; import { useContext, useState } from 'react'; -import { AuthContext } from '../utils/AuthProvider.tsx'; +import { AuthContext } from '@/utils/AuthContext.tsx'; import LoadingWheel from '../components/chat/LoadingWheel.tsx'; import { axiosClient } from '@/utils/axiosClient.ts'; diff --git a/client/src/types/types.ts b/client/src/types/types.ts index 4ac30fe..1ae871f 100644 --- a/client/src/types/types.ts +++ b/client/src/types/types.ts @@ -39,9 +39,9 @@ export type FileWithPreviewProps = { preview: string | null; }; -export type UsernameType = { +export type UserType = { username: string | null; - user_id: string | null; + id: string | null; }; export type ChatContextType = { diff --git a/client/src/utils/AuthContext.tsx b/client/src/utils/AuthContext.tsx new file mode 100644 index 0000000..1caed72 --- /dev/null +++ b/client/src/utils/AuthContext.tsx @@ -0,0 +1,16 @@ +import { createContext } from 'react'; +import { UserType } from '@/types/types.ts'; + +interface AuthContextType { + authorized: boolean; + isLoading: boolean; + user: UserType | null; + setAuthorized: (value: boolean) => void; +} + +export const AuthContext = createContext({ + authorized: false, + isLoading: true, + user: null, + setAuthorized: () => {}, +}); diff --git a/client/src/utils/AuthProvider.tsx b/client/src/utils/AuthProvider.tsx index 991d944..226b25a 100644 --- a/client/src/utils/AuthProvider.tsx +++ b/client/src/utils/AuthProvider.tsx @@ -1,13 +1,48 @@ -import { createContext } from 'react'; +import { AuthContext } from './AuthContext'; +import Cookies from 'js-cookie'; +import { axiosClient } from '@/utils/axiosClient.ts'; +import { ReactNode, useEffect, useState } from 'react'; +import { UserType } from '@/types/types.ts'; -type AuthContextType = { - authorized: boolean; - isLoading: boolean; - setAuthorized: (value: boolean) => void; -}; +export function AuthProvider({ children }: { children: ReactNode }) { + const [authorized, setAuthorized] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState(null); + const token = Cookies.get('token'); -export const AuthContext = createContext({ - authorized: false, - isLoading: true, - setAuthorized: () => {}, -}); + useEffect(() => { + async function validateAuth() { + if (!token) { + setIsLoading(false); + return; + } + + try { + const response = await axiosClient.get('/api/auth/validate', { + withCredentials: true, + }); + setUser({ + username: response.data.username, + id: response.data.user_id, + }); + setAuthorized(true); + } catch (error) { + console.error('Auth validation failed:', error); + setAuthorized(false); + setUser(null); + } finally { + setIsLoading(false); + } + } + + validateAuth(); + }, [token]); + + return ( + + {children} + + ); +} diff --git a/client/src/utils/ProtectedRoutes.tsx b/client/src/utils/ProtectedRoutes.tsx index e09acfb..5dd4d87 100644 --- a/client/src/utils/ProtectedRoutes.tsx +++ b/client/src/utils/ProtectedRoutes.tsx @@ -1,34 +1,15 @@ import { Navigate, Outlet } from 'react-router-dom'; -import { useContext, useEffect, useState } from 'react'; -import { AuthContext } from './AuthProvider.tsx'; -import LoadingScreen from '../components/LoadingScreen.tsx'; -import { UsernameType } from '@/types/types.ts'; -import { axiosClient } from '@/utils/axiosClient.ts'; +import { useAuth } from '@/utils/useAuth.tsx'; +import LoadingScreen from '@/components/LoadingScreen'; function ProtectedRoutes() { - const { authorized, isLoading } = useContext(AuthContext); - const [user, setUser] = useState({ - username: null, - user_id: null, - }); - - useEffect(() => { - if (authorized) { - axiosClient - .get('/api/auth/validate', { withCredentials: true }) - .then((res) => { - setUser(res.data); - }) - .catch(console.error); - } - }, [authorized]); - + const { authorized, isLoading, user } = useAuth(); if (isLoading) { return ; } return authorized ? ( - + ) : ( ); diff --git a/client/src/utils/PublicRoute.tsx b/client/src/utils/PublicRoute.tsx index d784e91..850c890 100644 --- a/client/src/utils/PublicRoute.tsx +++ b/client/src/utils/PublicRoute.tsx @@ -1,10 +1,9 @@ import { Navigate, Outlet } from 'react-router-dom'; -import { useContext } from 'react'; -import { AuthContext } from './AuthProvider'; -import LoadingScreen from '../components/LoadingScreen'; +import { useAuth } from '@/utils/useAuth.tsx'; +import LoadingScreen from '@/components/LoadingScreen'; function PublicRoute() { - const { authorized, isLoading } = useContext(AuthContext); + const { authorized, isLoading } = useAuth(); if (isLoading) { return ; diff --git a/client/src/utils/useAuth.tsx b/client/src/utils/useAuth.tsx index 44db8f4..239ef19 100644 --- a/client/src/utils/useAuth.tsx +++ b/client/src/utils/useAuth.tsx @@ -1,33 +1,4 @@ -import { useState, useEffect } from 'react'; -import Cookies from 'js-cookie'; +import { useContext } from 'react'; +import { AuthContext } from '@/utils/AuthContext.tsx'; -import { axiosClient } from '@/utils/axiosClient.ts'; - -function useAuth() { - const [authorized, setAuthorized] = useState(false); - - useEffect(() => { - async function validateToken() { - const token = Cookies.get('token'); - if (!token) { - setAuthorized(false); - return; - } - - try { - await axiosClient.get('/api/auth/validate', { withCredentials: true }); - - setAuthorized(true); - } catch (e) { - setAuthorized(false); - console.log('Failed to validate token: ', e); - } - } - - validateToken(); - }, []); - - return authorized; -} - -export default useAuth; +export const useAuth = () => useContext(AuthContext); diff --git a/docker-compose.yml b/docker-compose.yml index c6549f6..91f05cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,8 @@ services: PG_HOST: db ports: - "3000" + volumes: + - attachments:/app/client/server/attachments depends_on: db: condition: service_healthy