feat: improve chat realtime and media composer UX
All checks were successful
CI / test (push) Successful in 27s
All checks were successful
CI / test (push) Successful in 27s
- add media preview and upload confirmation for image/video - add upload progress tracking for presigned uploads - keep voice recording/upload flow with better UI states - include related realtime/chat updates currently in working tree
This commit is contained in:
@@ -4,10 +4,10 @@ import { searchUsers } from "../api/users";
|
||||
import type { ChatType, UserSearchItem } from "../chat/types";
|
||||
import { useChatStore } from "../store/chatStore";
|
||||
|
||||
type CreateMode = "private" | "group" | "channel";
|
||||
type CreateMode = "group" | "channel";
|
||||
|
||||
export function NewChatPanel() {
|
||||
const [mode, setMode] = useState<CreateMode>("private");
|
||||
const [mode, setMode] = useState<CreateMode>("group");
|
||||
const [query, setQuery] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const [results, setResults] = useState<UserSearchItem[]>([]);
|
||||
@@ -57,9 +57,6 @@ export function NewChatPanel() {
|
||||
|
||||
async function createByType(event: FormEvent) {
|
||||
event.preventDefault();
|
||||
if (mode === "private") {
|
||||
return;
|
||||
}
|
||||
if (!title.trim()) {
|
||||
setError("Title is required");
|
||||
return;
|
||||
@@ -80,9 +77,6 @@ export function NewChatPanel() {
|
||||
return (
|
||||
<div className="border-b border-slate-700 p-3">
|
||||
<div className="mb-2 flex gap-2 text-xs">
|
||||
<button className={`rounded px-2 py-1 ${mode === "private" ? "bg-accent text-black" : "bg-slate-700"}`} onClick={() => setMode("private")}>
|
||||
Private
|
||||
</button>
|
||||
<button className={`rounded px-2 py-1 ${mode === "group" ? "bg-accent text-black" : "bg-slate-700"}`} onClick={() => setMode("group")}>
|
||||
Group
|
||||
</button>
|
||||
@@ -91,40 +85,38 @@ export function NewChatPanel() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{mode === "private" ? (
|
||||
<div className="space-y-2">
|
||||
<input
|
||||
className="w-full rounded bg-slate-800 px-2 py-1 text-sm"
|
||||
placeholder="@username"
|
||||
value={query}
|
||||
onChange={(e) => void handleSearch(e.target.value)}
|
||||
/>
|
||||
<div className="max-h-32 overflow-auto">
|
||||
{results.map((user) => (
|
||||
<button
|
||||
className="mb-1 block w-full rounded bg-slate-800 px-2 py-1 text-left text-sm hover:bg-slate-700"
|
||||
key={user.id}
|
||||
onClick={() => void createPrivate(user.id)}
|
||||
>
|
||||
@{user.username}
|
||||
</button>
|
||||
))}
|
||||
{normalizedQuery && results.length === 0 ? <p className="text-xs text-slate-400">No users</p> : null}
|
||||
</div>
|
||||
<div className="mb-3 space-y-2">
|
||||
<p className="text-xs text-slate-400">Новый диалог</p>
|
||||
<input
|
||||
className="w-full rounded bg-slate-800 px-2 py-1 text-sm"
|
||||
placeholder="@username"
|
||||
value={query}
|
||||
onChange={(e) => void handleSearch(e.target.value)}
|
||||
/>
|
||||
<div className="max-h-32 overflow-auto">
|
||||
{results.map((user) => (
|
||||
<button
|
||||
className="mb-1 block w-full rounded bg-slate-800 px-2 py-1 text-left text-sm hover:bg-slate-700"
|
||||
key={user.id}
|
||||
onClick={() => void createPrivate(user.id)}
|
||||
>
|
||||
@{user.username}
|
||||
</button>
|
||||
))}
|
||||
{normalizedQuery && results.length === 0 ? <p className="text-xs text-slate-400">No users</p> : null}
|
||||
</div>
|
||||
) : (
|
||||
<form className="space-y-2" onSubmit={(e) => void createByType(e)}>
|
||||
<input
|
||||
className="w-full rounded bg-slate-800 px-2 py-1 text-sm"
|
||||
placeholder={mode === "group" ? "Group title" : "Channel title"}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<button className="w-full rounded bg-slate-700 px-2 py-1 text-sm hover:bg-slate-600" disabled={loading} type="submit">
|
||||
Create {mode}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
<form className="space-y-2" onSubmit={(e) => void createByType(e)}>
|
||||
<input
|
||||
className="w-full rounded bg-slate-800 px-2 py-1 text-sm"
|
||||
placeholder={mode === "group" ? "Group title" : "Channel title"}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
<button className="w-full rounded bg-slate-700 px-2 py-1 text-sm hover:bg-slate-600" disabled={loading} type="submit">
|
||||
Create {mode}
|
||||
</button>
|
||||
</form>
|
||||
{error ? <p className="mt-2 text-xs text-red-400">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user