code refactor
This commit is contained in:
@@ -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: (
|
||||
<Suspense fallback={<LoadingScreen />}>
|
||||
<ChatProvider>
|
||||
<Chat />
|
||||
</ChatProvider>
|
||||
</Suspense>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -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 (
|
||||
<AuthContext.Provider value={{ authorized, isLoading, setAuthorized }}>
|
||||
<AuthProvider>
|
||||
<RouterProvider router={router} />
|
||||
</AuthContext.Provider>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{message.sender === currentUsername || isAdmin ? (
|
||||
{me.isGroupOwner ||
|
||||
message.sender == user.username ||
|
||||
(me.isGroupAdmin &&
|
||||
me.isGroupOwner &&
|
||||
message.sender_id !== groupOwner) ? (
|
||||
<Trash2
|
||||
className="opacity-0 group-hover:opacity-100 h-5 w-5 ml-2 flex-shrink-0 cursor-pointer text-gray-400 hover:text-red-500 transition-colors duration-200"
|
||||
onClick={handleDelete}
|
||||
|
||||
@@ -109,7 +109,8 @@ const MessageForm = () => {
|
||||
};
|
||||
|
||||
const submitMessage: SubmitHandler<InputProps> = 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) {
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const user: UsernameType = useOutletContext();
|
||||
const user: UserType = useOutletContext();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [shouldScrollToBottom, setShouldScrollToBottom] = useState(true);
|
||||
const previousMessagesLength = useRef(messages.length);
|
||||
@@ -223,13 +222,7 @@ function MessagesArea() {
|
||||
<ul className="flex-grow list-none">
|
||||
{isLoading ? <LoadingWheel /> : null}
|
||||
{messages.map((msg: ChatMessagesProps) => (
|
||||
<AnimatedMessage
|
||||
key={msg.message_id}
|
||||
message={msg}
|
||||
isAdmin={me.isGroupAdmin || me.isGroupOwner}
|
||||
currentUsername={user.username}
|
||||
onDelete={deleteMessage}
|
||||
/>
|
||||
<AnimatedMessage message={msg} onDelete={deleteMessage} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="dropdown dropdown-top border-t border-zinc-800">
|
||||
<div
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@/components/ui/context-menu.tsx';
|
||||
import { useOutletContext } from 'react-router-dom';
|
||||
import zdjecie from '../../../../assets/turtleProfileImg3.webp';
|
||||
import { ParticipantsProps, UsernameType } from '@/types/types.ts';
|
||||
import { ParticipantsProps, UserType } from '@/types/types.ts';
|
||||
import { useChat } from '@/context/chat/useChat.ts';
|
||||
import { axiosClient } from '@/utils/axiosClient.ts';
|
||||
|
||||
@@ -17,7 +17,7 @@ function ParticipantsBar() {
|
||||
const { setMe, initializeContact, setGroupOwner, currentContact, me } =
|
||||
useChat();
|
||||
const [participants, setParticipants] = useState<ParticipantsProps[]>([]);
|
||||
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() {
|
||||
</span>
|
||||
</li>
|
||||
</ContextMenuTrigger>
|
||||
{user.user_id !== participant.user_id &&
|
||||
{user.id !== participant.user_id &&
|
||||
me.isGroupAdmin &&
|
||||
(!participant.isadmin || me.isGroupOwner) ? (
|
||||
<ContextMenuContent className="p-0">
|
||||
@@ -298,7 +299,6 @@ function ParticipantsBar() {
|
||||
<h1 className="text-xl font-semibold text-gray-200 text-left p-4 border-b border-zinc-800">
|
||||
Members
|
||||
</h1>
|
||||
|
||||
<ul className=" rounded">{ParticipantsList}</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
16
client/src/utils/AuthContext.tsx
Normal file
16
client/src/utils/AuthContext.tsx
Normal file
@@ -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<AuthContextType>({
|
||||
authorized: false,
|
||||
isLoading: true,
|
||||
user: null,
|
||||
setAuthorized: () => {},
|
||||
});
|
||||
@@ -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<UserType | null>(null);
|
||||
const token = Cookies.get('token');
|
||||
|
||||
export const AuthContext = createContext<AuthContextType>({
|
||||
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 (
|
||||
<AuthContext.Provider
|
||||
value={{ authorized, isLoading, user, setAuthorized }}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<UsernameType>({
|
||||
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 <LoadingScreen />;
|
||||
}
|
||||
|
||||
return authorized ? (
|
||||
<Outlet context={user satisfies UsernameType} />
|
||||
<Outlet context={user} />
|
||||
) : (
|
||||
<Navigate to="/login" replace />
|
||||
);
|
||||
|
||||
@@ -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 <LoadingScreen />;
|
||||
|
||||
@@ -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<boolean>(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);
|
||||
|
||||
@@ -13,6 +13,8 @@ services:
|
||||
PG_HOST: db
|
||||
ports:
|
||||
- "3000"
|
||||
volumes:
|
||||
- attachments:/app/client/server/attachments
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
Reference in New Issue
Block a user