code refactor

This commit is contained in:
slawk0
2025-01-02 20:10:08 +01:00
parent c2b53e1c6c
commit 0ac655b83a
16 changed files with 130 additions and 156 deletions

View File

@@ -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>
);
}

View File

@@ -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}

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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

View File

@@ -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>
);

View File

@@ -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 &&

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 = {

View 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: () => {},
});

View File

@@ -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>
);
}

View File

@@ -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 />
);

View File

@@ -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 />;

View File

@@ -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);

View File

@@ -13,6 +13,8 @@ services:
PG_HOST: db
ports:
- "3000"
volumes:
- attachments:/app/client/server/attachments
depends_on:
db:
condition: service_healthy