Add web client and containerized deployment stack
All checks were successful
CI / test (push) Successful in 19s

Web client:

- Added React + TypeScript + Vite + Tailwind application in web/.

- Implemented auth, chat list, chat messages, typing indicators, file uploads, and voice recording/playback.

- Added typed API layer, Zustand stores, and realtime websocket hook integration.

Containerization:

- Added backend Dockerfile and project .dockerignore.

- Added web multi-stage Dockerfile with nginx static hosting and API/WS reverse proxy.

- Added full docker-compose stack with postgres, redis, minio, backend, worker, mailpit, and web.

- Added MinIO bucket bootstrap init job and updated README with Docker quick-start.
This commit is contained in:
2026-03-07 21:55:50 +03:00
parent 85631b566a
commit 2501466c7a
35 changed files with 4074 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
import { useMemo } from "react";
import { useAuthStore } from "../store/authStore";
import { useChatStore } from "../store/chatStore";
import { formatTime } from "../utils/format";
export function MessageList() {
const me = useAuthStore((s) => s.me);
const activeChatId = useChatStore((s) => s.activeChatId);
const messagesByChat = useChatStore((s) => s.messagesByChat);
const typingByChat = useChatStore((s) => s.typingByChat);
const messages = useMemo(() => {
if (!activeChatId) {
return [];
}
return messagesByChat[activeChatId] ?? [];
}, [activeChatId, messagesByChat]);
if (!activeChatId) {
return <div className="flex h-full items-center justify-center text-slate-400">Select a chat</div>;
}
return (
<div className="flex h-full flex-col">
<div className="flex-1 overflow-auto p-4">
{messages.map((message) => (
<div className={`mb-3 flex ${message.sender_id === me?.id ? "justify-end" : "justify-start"}`} key={message.id}>
<div className="max-w-[80%] rounded-lg bg-slate-800 px-3 py-2">
{message.type === "voice" && message.text ? (
<audio controls src={message.text} />
) : (
<p className="whitespace-pre-wrap break-words">{message.text}</p>
)}
<p className="mt-1 text-right text-[11px] text-slate-400">{formatTime(message.created_at)}</p>
</div>
</div>
))}
</div>
<div className="px-4 pb-2 text-xs text-slate-400">
{(typingByChat[activeChatId] ?? []).length > 0 ? "Someone is typing..." : ""}
</div>
</div>
);
}