feat: Introduce runtime configuration for domain and enhance S3 storage settings with public base URL and Contabo support.

This commit is contained in:
Christomatt
2026-02-03 01:27:46 +01:00
parent 91d122ca3a
commit 38ddbf8bc1
20 changed files with 244 additions and 48 deletions
+3 -2
View File
@@ -5,7 +5,7 @@ import AutoTextarea from '@/components/AutoTextarea';
import { Post, Attachment } from '@/lib/types';
import { ImageIcon, AlertTriangle, Film } from 'lucide-react';
import { VideoEmbed } from '@/components/VideoEmbed';
import { formatFullHandle } from '@/lib/utils/handle';
import { useFormattedHandle } from '@/lib/utils/handle';
import { useAuth } from '@/lib/contexts/AuthContext';
interface MediaAttachment extends Attachment {
@@ -22,6 +22,7 @@ interface ComposeProps {
export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What's happening?", isReply }: ComposeProps) {
const { isIdentityUnlocked } = useAuth();
const replyToHandle = replyingTo ? useFormattedHandle(replyingTo.author.handle) : '';
const [content, setContent] = useState('');
const [isPosting, setIsPosting] = useState(false);
const [attachments, setAttachments] = useState<MediaAttachment[]>([]);
@@ -161,7 +162,7 @@ export function Compose({ onPost, replyingTo, onCancelReply, placeholder = "What
{replyingTo && !isReply && (
<div className="compose-reply-target">
<div className="compose-reply-info">
Replying to <span className="compose-reply-handle">{formatFullHandle(replyingTo.author.handle)}</span>
Replying to <span className="compose-reply-handle">{replyToHandle}</span>
</div>
<button type="button" className="compose-reply-cancel" onClick={onCancelReply}>
Cancel
+10 -6
View File
@@ -10,7 +10,8 @@ import { useAuth } from '@/lib/contexts/AuthContext';
import { useToast } from '@/lib/contexts/ToastContext';
import { VideoEmbed } from '@/components/VideoEmbed';
import BlurredVideo from '@/components/BlurredVideo';
import { formatFullHandle, NODE_DOMAIN } from '@/lib/utils/handle';
import { useFormattedHandle } from '@/lib/utils/handle';
import { useDomain } from '@/lib/contexts/ConfigContext';
import { signedAPI } from '@/lib/api/signed-fetch';
// Component for link preview image that hides on error
@@ -52,6 +53,9 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
const [reporting, setReporting] = useState(false);
const [deleting, setDeleting] = useState(false);
const [showMenu, setShowMenu] = useState(false);
const domain = useDomain();
const authorHandle = useFormattedHandle(post.author.handle, post.nodeDomain);
const replyToHandle = post.replyTo?.author?.handle ? useFormattedHandle(post.replyTo.author.handle) : '';
// Sync state if post changes (e.g. after a re-render from parent)
useEffect(() => {
@@ -263,7 +267,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
return post.author.handle;
}
// If this is a swarm post from a DIFFERENT node, append the node domain
if (post.nodeDomain && post.nodeDomain !== NODE_DOMAIN) {
if (post.nodeDomain && post.nodeDomain !== domain) {
return `${post.author.handle}@${post.nodeDomain}`;
}
// Local user
@@ -403,7 +407,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
<Link href={`/u/${profileHandle}`} className="post-handle" onClick={(e) => e.stopPropagation()}>
{post.author.displayName || post.author.handle}
</Link>
<span className="post-time">{formatFullHandle(post.author.handle, post.nodeDomain)}</span>
<span className="post-time">{authorHandle}</span>
</div>
</div>
<div className="post-content">{renderContent(post.content, post.linkPreviewUrl ?? undefined)}</div>
@@ -467,7 +471,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
</span>
)}
</div>
<span className="post-time">{formatFullHandle(post.author.handle, post.nodeDomain)} · {formatTime(post.createdAt)}</span>
<span className="post-time">{authorHandle} · {formatTime(post.createdAt)}</span>
</div>
{currentUser && currentUser.id !== post.author.id && (
<div style={{ position: 'relative', marginLeft: 'auto' }}>
@@ -591,7 +595,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
{effectiveReplyTo && !showThread && (
<div className="post-reply-to">
Replying to <Link href={`/u/${effectiveReplyTo.author.handle}`} onClick={(e) => e.stopPropagation()}>{formatFullHandle(effectiveReplyTo.author.handle)}</Link>
Replying to <Link href={`/u/${effectiveReplyTo.author.handle}`} onClick={(e) => e.stopPropagation()}>{replyToHandle}</Link>
</div>
)}
@@ -672,7 +676,7 @@ export function PostCard({ post, onLike, onRepost, onComment, onDelete, onHide,
// Allow deleting own remote posts where handle might be username@node_domain
(post.author.id.startsWith('swarm:') && (
post.author.handle === currentUser.handle ||
post.author.handle === `${currentUser.handle}@${NODE_DOMAIN}`
post.author.handle === `${currentUser.handle}@${domain}`
))
)) && (
<button className="post-action delete-action" onClick={handleDelete} disabled={deleting} title="Delete post">
+3 -2
View File
@@ -6,7 +6,7 @@ import Image from 'next/image';
import { usePathname, useRouter } from 'next/navigation';
import { useAuth } from '@/lib/contexts/AuthContext';
import { HomeIcon, SearchIcon, BellIcon, UserIcon, ShieldIcon, SettingsIcon, BotIcon } from './Icons';
import { formatFullHandle } from '@/lib/utils/handle';
import { useFormattedHandle } from '@/lib/utils/handle';
import { LogOut, Settings2 } from 'lucide-react';
// import { IdentityUnlockPrompt } from './IdentityUnlockPrompt'; // Moved to LayoutWrapper
@@ -18,6 +18,7 @@ export function Sidebar() {
const [unreadCount, setUnreadCount] = useState(0);
const [unreadChatCount, setUnreadChatCount] = useState(0);
const [loggingOut, setLoggingOut] = useState(false);
const formattedHandle = user ? useFormattedHandle(user.handle) : '';
useEffect(() => {
fetch('/api/node')
@@ -188,7 +189,7 @@ export function Sidebar() {
</div>
<div style={{ minWidth: 0, flex: 1 }}>
<div style={{ fontWeight: 600, fontSize: '14px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{user.displayName}</div>
<div style={{ color: 'var(--foreground-tertiary)', fontSize: '13px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{formatFullHandle(user.handle)}</div>
<div style={{ color: 'var(--foreground-tertiary)', fontSize: '13px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{formattedHandle}</div>
</div>
</div>