From ef502f4d44bee7b64368dd82fa21c502ee1b3d8d Mon Sep 17 00:00:00 2001 From: Delqhi Date: Sat, 13 Jun 2026 13:48:24 +0200 Subject: [PATCH] fix(ui): v0-strict sidebar dropdown + chat-view hero (functional preserved) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #65 follow-up — keep all functional extensions (SIN_MODELS, Project selector, Sparkles Enhance, Suggestion chips for repo tasks) but render the layout/typography/icons 100% v0-faithful. UserMenu (components/auth/user-menu.tsx): - Dropdown is now ALWAYS shown — even for unauthenticated users - Trigger displays '?' avatar + 'Anmelden' (was: a separate Anmelden button that bypassed the dropdown entirely, #65 regression) - Dropdown header shows 'Nicht angemeldet' + 'Melde dich an, um Chats zu speichern' when no session - Footer item toggles between 'Sign Out' and 'Anmelden' based on auth - All 7 v0 links (Profile, Account Settings, Pricing, Documentation, Feedback, Community Forum, Refer a Friend) + Preferences section with Theme (System/Light/Dark 3-segment), Language, Chat Position ChatView (components/chat/chat-view.tsx): - Restored Starburst icon + 'What do you want to create?' hero (lost in earlier refactor) - Added 'Press Enter to send' hint with kbd element - Width switches from max-w-3xl (with messages) to full (empty state) - Preserved all AI SDK 6 wiring (useChat, sendMessage, parts, etc.) --- components/auth/user-menu.tsx | 133 ++++++++++++++++++++-------------- components/chat/chat-view.tsx | 75 +++++++++++++++++-- 2 files changed, 147 insertions(+), 61 deletions(-) diff --git a/components/auth/user-menu.tsx b/components/auth/user-menu.tsx index 3998c42..f2b68b1 100644 --- a/components/auth/user-menu.tsx +++ b/components/auth/user-menu.tsx @@ -9,6 +9,7 @@ import { CircleUser, CreditCard, Gift, + LogIn, LogOut, MessageCircleQuestion, Monitor, @@ -31,7 +32,7 @@ import { const FOOTER_LINKS = [ { href: "/settings/preferences", label: "Profile", Icon: CircleUser }, { href: "/settings", label: "Account Settings", Icon: SettingsIcon }, - { href: "/settings/billing", label: "Pricing", Icon: CreditCard }, + { href: "/settings/billing", label: "Pricing", Icon: CreditCard, external: true }, { href: "https://github.com/OpenSIN-Code/SIN-Code-WebUI-v2#readme", label: "Documentation", @@ -58,35 +59,19 @@ export function UserMenu() { const { data: session, isPending } = useSession() const { theme, setTheme } = useTheme() - if (isPending) { - return ( -
- - … - -
- ) - } - - if (!session?.user) { - return ( - - ) - } - - const user = session.user as { name?: string | null; email?: string | null } - const displayName = user.name || user.email || "User" - const initial = (user.name || user.email || "U").charAt(0).toUpperCase() - const email = user.email || "" + // Show trigger even while pending / unauthenticated — the v0-style dropdown + // is always present at the sidebar footer. + const user = session?.user as + | { name?: string | null; email?: string | null } + | undefined + const isLoggedIn = Boolean(user?.name || user?.email) + const displayName = isLoggedIn + ? user!.name || user!.email || "User" + : "Anmelden" + const initial = isLoggedIn + ? (user!.name || user!.email || "U").charAt(0).toUpperCase() + : "?" + const email = user?.email || "" async function handleSignOut() { const { signOut } = await import("@/lib/auth/client") @@ -95,32 +80,50 @@ export function UserMenu() { router.refresh() } + function handleSignIn() { + router.push("/login") + } + return ( } > - {initial} + {isPending ? "…" : initial} {displayName} -
- - {displayName} - - {email ? ( - {email} - ) : null} -
+ {isLoggedIn ? ( +
+ + {displayName} + + {email ? ( + {email} + ) : null} +
+ ) : ( +
+ + Nicht angemeldet + + + Melde dich an, um Chats zu speichern + +
+ )} @@ -129,7 +132,12 @@ export function UserMenu() { key={label} render={ external ? ( - + {label} @@ -194,7 +202,7 @@ export function UserMenu() { - {/* Chat Position — stub (single layout) */} + {/* Chat Position — stub */}
Chat Position - - - Sign Out - - } - /> + {isLoggedIn ? ( + + + Sign Out + + } + /> + ) : ( + + + Anmelden + + } + /> + )} diff --git a/components/chat/chat-view.tsx b/components/chat/chat-view.tsx index f20710c..a7d2076 100644 --- a/components/chat/chat-view.tsx +++ b/components/chat/chat-view.tsx @@ -1,12 +1,15 @@ "use client" -import { useRef, useEffect } from "react" +import { useRef, useEffect, useState } from "react" +import { Check, Copy, SquareTerminal } from "lucide-react" import { Message } from "@/components/chat/message" import { ThinkingIndicator, LoadingDots } from "@/components/chat/thinking-indicator" import { ToolCall } from "@/components/chat/tool-call" import { PromptComposer } from "@/components/chat/prompt-composer" import { MarkdownMessage } from "@/components/chat/markdown-message" import { ChatHeader } from "@/components/chat/chat-header" +import { DashedSpinner, Starburst } from "@/components/icons" +import { cn } from "@/lib/utils" export interface ChatPart { type: "text" | "tool" @@ -32,6 +35,51 @@ interface ChatViewProps { title?: string } +/* v0-style code block with copy button (preserved from initial commit) */ +function CopyCodeBlock({ body, lang = "code" }: { body: string; lang?: string }) { + const [copied, setCopied] = useState(false) + async function handleCopy() { + try { + await navigator.clipboard.writeText(body) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } catch { + /* clipboard not available */ + } + } + return ( +
+
+
+ + {lang} +
+ +
+
+        {body}
+      
+
+ ) +} + +/* v0-style tool badge (preserved from initial commit) */ +function ToolBadge({ name }: { name: string }) { + return ( +
+ + {name} +
+ ) +} + export function ChatView({ messages, status, @@ -48,19 +96,22 @@ export function ChatView({ const isStreaming = status === "streaming" const lastMessage = messages[messages.length - 1] + const hasMessages = messages.length > 0 return (
-
- {messages.length === 0 && ( -
-

+
+ {!hasMessages && ( +
+ +

What do you want to create?

-

+

Ask the SIN-Code agent to build, fix, or explain anything. + Press Enter to send.

)} @@ -68,6 +119,18 @@ export function ChatView({ {messages.map((msg) => { const streamingThis = isStreaming && msg.id === lastMessage?.id && msg.role === "assistant" + if (msg.role === "user") { + return ( +
+
+ {msg.parts + .filter((p) => p.type === "text") + .map((p) => p.text) + .join("")} +
+
+ ) + } return (