Profiles, Giphy, uploads, settings UI
Backend: - Auth: split routes into RegisterPublicRoutes + RegisterProtectedRoutes - Auth: PATCH /me for profile updates (display_name, bio, accent_color, status_text) - Auth: Gravatar fallback for avatars on register - DB: users table now has bio, accent_color, status_text columns - giphy/client.go: Search() and GetTrending() against Giphy API - upload/handlers.go: MinIO file upload + serve - config: added GiphyConfig and MinIO config - cmd/server: wired giphy (/gifs/search, /gifs/trending), upload (/upload), files (/files/*) routes Frontend: - auth store: updated User interface with new profile fields, added updateProfile() - UserSettings.tsx: terminal-styled profile editor (display_name, bio, accent_color, status_text, avatar upload) - GiphyPicker.tsx: terminal-styled GIF picker with search + trending - ChatArea.tsx: integrated [GIF] button into message input - App.tsx: imports UserSettings, added /settings route
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useMessageStore } from '../stores/message.ts';
|
||||
import { useChannelStore } from '../stores/channel.ts';
|
||||
import { GiphyPicker, type Gif } from './GiphyPicker';
|
||||
|
||||
function formatTime(iso: string): string {
|
||||
const date = new Date(iso);
|
||||
@@ -18,6 +19,7 @@ export function ChatArea() {
|
||||
const fetchMessages = useMessageStore((state) => state.fetchMessages);
|
||||
const sendMessage = useMessageStore((state) => state.sendMessage);
|
||||
const [input, setInput] = useState('');
|
||||
const [showGifPicker, setShowGifPicker] = useState(false);
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,6 +41,13 @@ export function ChatArea() {
|
||||
setInput('');
|
||||
};
|
||||
|
||||
const handleGifSelect = async (gif: Gif) => {
|
||||
if (!activeChannelId) return;
|
||||
const content = ``;
|
||||
await sendMessage(activeChannelId, content);
|
||||
setShowGifPicker(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-gb-bg">
|
||||
<div className="terminal-border border-t-0 border-x-0 px-3 py-2 text-gb-fg-s">
|
||||
@@ -58,6 +67,14 @@ export function ChatArea() {
|
||||
))}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
{showGifPicker && (
|
||||
<div className="px-3 pb-1">
|
||||
<GiphyPicker
|
||||
onSelect={handleGifSelect}
|
||||
onClose={() => setShowGifPicker(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="p-3 flex items-center gap-2">
|
||||
<span className="text-gb-fg-f select-none">{'>'}</span>
|
||||
<input
|
||||
@@ -68,6 +85,15 @@ export function ChatArea() {
|
||||
className="terminal-input"
|
||||
disabled={!activeChannelId}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowGifPicker((prev) => !prev)}
|
||||
disabled={!activeChannelId}
|
||||
className="text-gb-fg-f hover:text-gb-orange font-mono text-sm select-none disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Toggle GIF picker"
|
||||
>
|
||||
[GIF]
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user