Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/(auth)/login/_components/DesktopForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';
import GoogleLoginButton from '@/app/_components/GoogleLoginButton';
import { useFormStatus } from 'react-dom';
interface Props {
setMode: (mode: string) => void;
Expand Down Expand Up @@ -44,6 +45,8 @@ export default function DesktopForm({ setMode, registerAction, loginAction }: Pr
/>
</div>
<SubmitButton text="Ingresar a la plataforma" />

<GoogleLoginButton />
</form>
<div className="flex gap-1">
<p>¿Aún no tienes una cuenta?</p>
Expand All @@ -53,6 +56,7 @@ export default function DesktopForm({ setMode, registerAction, loginAction }: Pr
</div>
</section>

{/* REGISTRO */}
<section className="w-[100%] flex flex-col items-center gap-5 max-w-[600px] mx-auto">
<header className="flex flex-col items-center gap-1">
<h1 className="text-2xl font-extrabold text-center">Regístrate y Conecta</h1>
Expand Down
2 changes: 2 additions & 0 deletions app/(auth)/login/_components/MobileForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';
import GoogleLoginButton from '@/app/_components/GoogleLoginButton';
import { useState } from 'react';

interface Props {
Expand Down Expand Up @@ -48,6 +49,7 @@ export default function MobileForm({ registerAction, loginAction }: Props) {
/>
</div>
<button className="bg-purple rounded p-2">Ingresar a la plataforma</button>
<GoogleLoginButton />
</form>
<div className="flex gap-1">
<p>¿Aún no tienes una cuenta?</p>
Expand Down
6 changes: 2 additions & 4 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@ export default function Login() {
await login(payload);
router.push('/dashboard');
};

const handleRegister = async (payload: FormData) => {
await register(payload);
router.push('/dashboard');
};

return (
<section className="flex items-center min-h-screen w-full">
<Image
src="/squares/multicolor_dark.png"
alt="multicolor"
width={0}
height={0}
className={`hidden lg:block absolute w-[50vw] transition-all duration-700 ease-in-out ${mode === 'login' ? 'left-[-45%]' : 'left-0'} rounded-br-2xl rounded-tr-2xl`}
className={`hidden lg:block absolute w-[50vw] transition-all duration-700 ease-in-out ${mode === 'login' ? 'left-[-45%]' : 'left-0'} rounded-br-2xl rounded-tr-2xl bg-black`}
/>
<div className="w-full">
{/* Desktop */}
Expand All @@ -42,7 +40,7 @@ export default function Login() {
alt="multicolor"
width={0}
height={0}
className={`hidden lg:block absolute w-[50vw] transition-all duration-1000 ease-in-out ${mode === 'login' ? 'right-0' : 'right-[-45%]'} rounded-bl-2xl rounded-tl-2xl`}
className={`hidden lg:block absolute w-[50vw] transition-all duration-1000 ease-in-out ${mode === 'login' ? 'right-0' : 'right-[-45%]'} rounded-bl-2xl rounded-tl-2xl bg-black`}
/>
</section>
);
Expand Down
34 changes: 34 additions & 0 deletions app/(protected)/dashboard/_apis/chats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import myAxios from '@/app/_apis/myAxios.config';
import { IChat } from '@/app/_lib/_interfaces/IChat';
import { toast } from 'react-toastify';

export const createChat = async (otherUserId: string) => {
try {
const res = await myAxios.post<IChat>(`/api/v1/chat/direct/${otherUserId}`);
return res.data;
} catch (error) {
console.error('Error creating chat message:', error);
toast.error('Error al crear el chat');
return null;
}
};

export const getChatById = async (chatId: string) => {
try {
const res = await myAxios.get<IChat>(`/api/v1/chat/direct/${chatId}`);
return res.data;
} catch (error) {
console.error('Error fetching chat by ID:', error);
return null;
}
};

export const getAllChats = async () => {
try {
const res = await myAxios.get<IChat[]>('/api/v1/chat/direct/me');
return res.data;
} catch (error) {
console.error('Error fetching chats:', error);
return [];
}
};
10 changes: 10 additions & 0 deletions app/(protected)/dashboard/_apis/messages.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client';
import myAxios from '@/app/_apis/myAxios.config';
import { IMessage } from '@/app/_lib/_interfaces/IMessage';

Expand All @@ -10,3 +11,12 @@ export const getRoomMessages = async (roomId: string) => {
return [];
}
};

export const getChatMessages = async (chatId: string) => {
try {
const res = await myAxios.get<IMessage[]>(`/api/v1/chat/direct/${chatId}/messages`);
return res.data;
} catch {
return [];
}
};
3 changes: 1 addition & 2 deletions app/(protected)/dashboard/_apis/rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import { toast } from 'react-toastify';

export const createRoom = async (data: ICreateRoom) => {
const { name, description, userIds } = data;
console.log('data', data);
if (!name || !description || !userIds) {
toast.error('Por favor completa todos los campos');
return null;
}

try {
const res = await myAxios.post<ICreateRoom>('/api/v1/chat/rooms', data);
const res = await myAxios.post<IRoom>('/api/v1/chat/rooms', data);
toast.success('Sala creada con éxito');
return res.data;
} catch (error: any) {
Expand Down
32 changes: 32 additions & 0 deletions app/(protected)/dashboard/_components/ChatList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IChat } from '@/app/_lib/_interfaces/IChat';
import Link from 'next/link';

interface Props {
chats: IChat[];
}

export default function ChatList({ chats }: Props) {
return (
<>
{chats.map(sala => (
<article key={sala.id} className="w-full flex gap-5 items-center justify-between">
<Link
href={`/dashboard/chat/${sala.id}`}
className="flex gap-5 items-center justify-between bg-purple w-full p-3 rounded-2xl min-w-0 flex-11/12"
>
<p className="font-bold text-2xl lg:text-4xl truncate min-w-0 flex-1">
Chat con {sala.userIds[0]}
</p>
<div className="hidden sm:flex flex-col items-center flex-shrink-0">
<p className="font-semibold text-sm">
<span className="font-medium text-sm text-purple">
{new Date(sala.createdAt).toLocaleDateString('es-ES')}
</span>
</p>
</div>
</Link>
</article>
))}
</>
);
}
72 changes: 72 additions & 0 deletions app/(protected)/dashboard/_components/ChatsInit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { IChat } from '@/app/_lib/_interfaces/IChat';
import { IMessage } from '@/app/_lib/_interfaces/IMessage';
import { IRoom } from '@/app/_lib/_interfaces/IRoom';
import Message from '../room/_components/Message';
import { useState } from 'react';
import MessageInput from '../room/_components/MessageInput';
import MessagesInputBlocked from '../room/_components/MessagesInputBlocked';
import UserModal from './UserModal';
import { useAuth } from '@/app/_hooks/useAuth';
import useIsBottom from '@/app/_hooks/useScroll';

interface Props {
room?: IRoom;
chat?: IChat;
messages: IMessage[];
pendingMessages: IMessage[];
sendMessage: (msg: string) => void;
}

export default function ChatsInit({
room,
chat,
messages,
pendingMessages = [],
sendMessage,
}: Props) {
const user = useAuth(state => state.user);
const [userSelected, setUserSelected] = useState<{ userId: string; displayName: string } | null>(
null
);
const { containerRef, bottomRef } = useIsBottom({ items: [pendingMessages, messages] });

const handleUserSelect = (user: { userId: string; displayName: string }) => {
setUserSelected(user);
};

const handleCloseUserModal = () => {
setUserSelected(null);
};

const canSendMessage = () => {
if (room) {
return room.members.some(member => member === user?.uid);
}
if (chat) {
return true;
}
return false;
};

return (
<>
<div ref={containerRef} className="p-4 h-full flex flex-col gap-5 lg:gap-7 overflow-auto">
{messages.map(msg => (
<Message key={msg.id} message={msg} selectUser={handleUserSelect} />
))}
{pendingMessages.map(msg => (
<Message key={msg.id} message={msg} selectUser={handleUserSelect} />
))}
<div ref={bottomRef} />
</div>
<div className="p-4 px-6 bg-purple-2">
{canSendMessage() ? (
<MessageInput bottomRef={bottomRef} onSend={sendMessage} />
) : (
room && <MessagesInputBlocked room={room} />
)}
</div>
{userSelected && <UserModal user={userSelected} handleClose={handleCloseUserModal} />}
</>
);
}
4 changes: 2 additions & 2 deletions app/(protected)/dashboard/_components/RoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default function RoomList({ rooms }: Props) {
</p>
</div>
</Link>
<div className="flex gap-5 justify-end flex-2/12">
{/* <div className="flex gap-5 justify-end flex-2/12">
{CanExitRoom(sala) && (
<Tooltip title="Salir de la sala" placement="top">
<button className="bg-purple rounded-full">
Expand All @@ -45,7 +45,7 @@ export default function RoomList({ rooms }: Props) {
<DoorBellIcon className="w-10 h-10 cursor-pointer" />{' '}
</button>
</Tooltip>
</div>
</div> */}
</article>
))}
</>
Expand Down
56 changes: 56 additions & 0 deletions app/(protected)/dashboard/_components/UserModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { createChat } from '../_apis/chats';
import { useRouter } from 'next/navigation';

interface Props {
user: { userId: string; displayName: string } | null;
handleClose: () => void;
}
export default function UserModal({ handleClose, user }: Props) {
const queryClient = useQueryClient();
const router = useRouter();
const open = Boolean(user);

const handleClickOutside = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) {
handleClose();
}
};

const mutation = useMutation({
mutationFn: (data: string) => createChat(data),
onSuccess: res => {
if (!res) return;
queryClient.invalidateQueries({ queryKey: [`all-chats-dashboard`] });
router.push(`/dashboard/chat/${res.id}`);
},
});

if (!user) return null;

return (
<div
onClick={handleClickOutside}
className={`fixed inset-0 z-50 flex items-center justify-center bg-black/50 ${open ? 'block' : 'hidden'}`}
>
<div className="w-[80%] lg:w-[60%] flex flex-col gap-4 bg-semidarkpurple p-4 rounded-lg shadow-lg">
<h3 className="font-bold">Envía un mensaje directo</h3>
<p>Puedes establecer una conversación privada con {user.displayName}</p>
<div className="flex gap-2">
<button
className="cursor-pointer p-1 w-full rounded-md bg-lightblue"
onClick={handleClose}
>
Cerrar
</button>
<button
className="cursor-pointer p-1 w-full rounded-md bg-purple"
onClick={() => mutation.mutate(user.userId)}
>
Enviar mensaje
</button>
</div>
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions app/(protected)/dashboard/_hooks/useUserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { toast } from 'react-toastify';
export const useUserProfile = () => {
const { user } = useAuth();

console.log(user);

const [loading, setLoading] = useState(true);
const [userForm, setUserForm] = useState({
displayName: user?.displayName || '',
Expand Down
44 changes: 44 additions & 0 deletions app/(protected)/dashboard/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';
import { useQuery } from '@tanstack/react-query';
import LoadingEmoji from '@/app/_components/LoadingEmoji';
import { use } from 'react';
import MessagesList from '../_components/MessageList';
import { getChatById } from '../../_apis/chats';
import { useChatMessages } from '../hooks/useChatMessages';

interface RouterProps {
params: Promise<{
id: string;
}>;
}
export default function ChatPage({ params }: RouterProps) {
const id = use(params).id;

const { messages, loading } = useChatMessages({ id });

const { data: chat, isLoading: chatLoading } = useQuery({
queryKey: [`chat-${id}`],
queryFn: () => getChatById(id),
});

if (loading || chatLoading) return <LoadingEmoji />;

if (!chat) {
return (
<div className="w-full h-full flex justify-center items-center">
<h2 className="text-lg text-gray-500">Chat no encontrado</h2>
</div>
);
}

return (
<div className="grid grid-rows-[auto_1fr_auto] w-full h-full overflow-auto">
<div>
<div className="p-3 lg:p-5 bg-semidarkpurple w-full">
<h2 className="text-lg lg:text-2xl font-bold">Chat Directo</h2>
</div>
</div>
{chat && <MessagesList chat={chat} initial_messages={messages ?? []} />}
</div>
);
}
23 changes: 23 additions & 0 deletions app/(protected)/dashboard/chat/_components/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

import { IMessage } from '@/app/_lib/_interfaces/IMessage';
import { IChat } from '@/app/_lib/_interfaces/IChat';
import ChatsInit from '../../_components/ChatsInit';
import { useChatSocket } from '@/app/_hooks/useChatSocket';

interface Props {
chat: IChat;
initial_messages: IMessage[];
}

export default function MessagesList({ chat, initial_messages }: Props) {
const { messages, pendingMessages, sendMessage } = useChatSocket({ initial_messages, chat });
return (
<ChatsInit
messages={messages}
pendingMessages={pendingMessages}
sendMessage={sendMessage}
chat={chat}
/>
);
}
Loading