Fix UI — CSS variables, sidebar toggle with chevron, settings panel pushes content
This commit is contained in:
+5
-4
@@ -38,22 +38,23 @@
|
|||||||
[data-font="medium"] { --font-size: 15px; }
|
[data-font="medium"] { --font-size: 15px; }
|
||||||
[data-font="large"] { --font-size: 17px; }
|
[data-font="large"] { --font-size: 17px; }
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
html {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
font-family: var(--font-sans);
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Markdown styles */
|
|
||||||
.prose p { margin-bottom: 0.75em; }
|
.prose p { margin-bottom: 0.75em; }
|
||||||
.prose p:last-child { margin-bottom: 0; }
|
.prose p:last-child { margin-bottom: 0; }
|
||||||
.prose strong { font-weight: 500; color: var(--text); }
|
.prose strong { font-weight: 500; color: var(--text); }
|
||||||
.prose em { font-style: italic; }
|
.prose em { font-style: italic; }
|
||||||
.prose code {
|
.prose code {
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 0.88em;
|
font-size: 0.88em;
|
||||||
background: var(--bg3);
|
background: var(--bg3);
|
||||||
padding: 1px 5px;
|
padding: 1px 5px;
|
||||||
|
|||||||
+2
-2
@@ -39,8 +39,8 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" data-theme="light" data-font="medium" suppressHydrationWarning>
|
||||||
<body className={`${sans.variable} ${mono.variable}`}>
|
<body style={{ fontFamily: 'var(--font-sans, sans-serif)' }} className={`${sans.variable} ${mono.variable}`}>
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+43
-32
@@ -17,19 +17,26 @@ export default function Home() {
|
|||||||
setMessages,
|
setMessages,
|
||||||
currentId,
|
currentId,
|
||||||
settingsOpen,
|
settingsOpen,
|
||||||
|
setSettingsOpen,
|
||||||
sidebarOpen,
|
sidebarOpen,
|
||||||
setSidebarOpen,
|
setSidebarOpen,
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load settings from server
|
api.getSettings().then(s => {
|
||||||
api.getSettings().then(setSettings).catch(console.error);
|
setSettings(s);
|
||||||
// Load conversations
|
document.documentElement.setAttribute('data-theme', s.theme || 'light');
|
||||||
|
document.documentElement.setAttribute('data-font', s.font_size || 'medium');
|
||||||
|
}).catch(console.error);
|
||||||
api.getConversations().then(setConversations).catch(console.error);
|
api.getConversations().then(setConversations).catch(console.error);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load messages when conversation changes
|
document.documentElement.setAttribute('data-theme', settings.theme || 'light');
|
||||||
|
document.documentElement.setAttribute('data-font', settings.font_size || 'medium');
|
||||||
|
}, [settings.theme, settings.font_size]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
api.getMessages(currentId).then(setMessages).catch(console.error);
|
api.getMessages(currentId).then(setMessages).catch(console.error);
|
||||||
} else {
|
} else {
|
||||||
@@ -38,51 +45,58 @@ export default function Home() {
|
|||||||
}, [currentId]);
|
}, [currentId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex h-dvh overflow-hidden" style={{ background: 'var(--bg)' }}>
|
||||||
className="flex h-dvh overflow-hidden"
|
|
||||||
style={{ background: 'var(--bg)' }}
|
|
||||||
data-theme={settings.theme}
|
|
||||||
data-font={settings.font_size}
|
|
||||||
>
|
|
||||||
{/* Mobile sidebar overlay */}
|
{/* Mobile sidebar overlay */}
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-20 bg-black/40 md:hidden"
|
className="fixed inset-0 z-20 md:hidden"
|
||||||
|
style={{ background: 'rgba(0,0,0,0.4)' }}
|
||||||
onClick={() => setSidebarOpen(false)}
|
onClick={() => setSidebarOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar — desktop: toggleable, mobile: slide-over */}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
fixed inset-y-0 left-0 z-30 w-72 transform transition-transform duration-200
|
fixed inset-y-0 left-0 z-30 flex-shrink-0 transition-all duration-200
|
||||||
md:relative md:translate-x-0 md:w-64 md:flex-shrink-0
|
md:relative md:translate-x-0
|
||||||
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
${sidebarOpen ? 'translate-x-0 w-64' : '-translate-x-full md:translate-x-0'}
|
||||||
|
${sidebarOpen ? 'md:w-64' : 'md:w-0 md:overflow-hidden'}
|
||||||
`}
|
`}
|
||||||
style={{ background: 'var(--sidebar-bg)', borderRight: '1px solid var(--border)' }}
|
style={{
|
||||||
|
background: 'var(--sidebar-bg)',
|
||||||
|
borderRight: sidebarOpen ? '1px solid var(--border)' : 'none',
|
||||||
|
width: undefined,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<div className="w-64 h-full">
|
||||||
|
<Sidebar onClose={() => setSidebarOpen(false)} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main */}
|
{/* Main — shrinks when settings opens */}
|
||||||
<div className="flex flex-col flex-1 min-w-0">
|
<div
|
||||||
|
className="flex flex-col flex-1 min-w-0 transition-all duration-200"
|
||||||
|
style={{ marginRight: settingsOpen ? '340px' : '0' }}
|
||||||
|
>
|
||||||
{/* Topbar */}
|
{/* Topbar */}
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between px-4 py-3 flex-shrink-0"
|
className="flex items-center justify-between px-4 py-3 flex-shrink-0"
|
||||||
style={{ borderBottom: '1px solid var(--border)' }}
|
style={{ borderBottom: '1px solid var(--border)' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
{/* Hamburger — mobile only */}
|
{/* Sidebar toggle */}
|
||||||
<button
|
<button
|
||||||
className="md:hidden p-1 rounded"
|
|
||||||
style={{ color: 'var(--text3)' }}
|
|
||||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
|
style={{ color: 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', lineHeight: 1 }}
|
||||||
aria-label="Toggle sidebar"
|
aria-label="Toggle sidebar"
|
||||||
>
|
>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<rect y="3" width="20" height="2" rx="1"/>
|
{sidebarOpen
|
||||||
<rect y="9" width="20" height="2" rx="1"/>
|
? <polyline points="15 18 9 12 15 6" />
|
||||||
<rect y="15" width="20" height="2" rx="1"/>
|
: <polyline points="9 18 15 12 9 6" />
|
||||||
|
}
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<span style={{ fontSize: '13px', color: 'var(--text3)' }}>
|
<span style={{ fontSize: '13px', color: 'var(--text3)' }}>
|
||||||
@@ -91,22 +105,19 @@ export default function Home() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => useStore.getState().setSettingsOpen(!settingsOpen)}
|
onClick={() => setSettingsOpen(!settingsOpen)}
|
||||||
style={{ color: 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', fontSize: '18px' }}
|
style={{ color: settingsOpen ? 'var(--accent)' : 'var(--text3)', background: 'none', border: 'none', cursor: 'pointer', padding: '4px', borderRadius: '6px', fontSize: '18px' }}
|
||||||
aria-label="Settings"
|
aria-label="Settings"
|
||||||
>
|
>
|
||||||
⚙
|
⚙
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Messages */}
|
|
||||||
<MessageList />
|
<MessageList />
|
||||||
|
|
||||||
{/* Input */}
|
|
||||||
<MessageInput />
|
<MessageInput />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings panel */}
|
{/* Settings panel — pushes content, doesn't cover */}
|
||||||
<SettingsPanel />
|
<SettingsPanel />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,20 +60,12 @@ export default function SettingsPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Backdrop on mobile */}
|
|
||||||
{settingsOpen && (
|
|
||||||
<div
|
|
||||||
className="fixed inset-0 z-40 bg-black/40 md:hidden"
|
|
||||||
onClick={() => setSettingsOpen(false)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`fixed top-0 right-0 bottom-0 z-50 flex flex-col transition-transform duration-200 ${
|
className={`fixed top-0 right-0 bottom-0 z-50 flex flex-col transition-transform duration-200 ${
|
||||||
settingsOpen ? 'translate-x-0' : 'translate-x-full'
|
settingsOpen ? 'translate-x-0' : 'translate-x-full'
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
width: 'min(340px, 100vw)',
|
width: '340px',
|
||||||
background: 'var(--bg)',
|
background: 'var(--bg)',
|
||||||
borderLeft: '1px solid var(--border)',
|
borderLeft: '1px solid var(--border)',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function groupConversations(convs: { id: string; title: string; updated_at: stri
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar({ onClose }: { onClose?: () => void }) {
|
||||||
const { conversations, currentId, setCurrentId, setMessages, setConversations, setSidebarOpen } = useStore();
|
const { conversations, currentId, setCurrentId, setMessages, setConversations, setSidebarOpen } = useStore();
|
||||||
|
|
||||||
async function newConversation() {
|
async function newConversation() {
|
||||||
@@ -36,7 +36,7 @@ export default function Sidebar() {
|
|||||||
setConversations(updated);
|
setConversations(updated);
|
||||||
setCurrentId(conv.id);
|
setCurrentId(conv.id);
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
setSidebarOpen(false);
|
if (onClose) onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadConversation(id: string) {
|
async function loadConversation(id: string) {
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import type { NextRequest } from 'next/server';
|
|||||||
export function proxy(request: NextRequest) {
|
export function proxy(request: NextRequest) {
|
||||||
const { pathname } = request.nextUrl;
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
// Allow login page and public assets
|
// Always allow these through
|
||||||
if (
|
if (
|
||||||
|
pathname.startsWith('/api/') ||
|
||||||
pathname.startsWith('/login') ||
|
pathname.startsWith('/login') ||
|
||||||
pathname.startsWith('/_next') ||
|
pathname.startsWith('/_next') ||
|
||||||
pathname.startsWith('/manifest.json') ||
|
pathname.startsWith('/manifest.json') ||
|
||||||
@@ -15,7 +16,7 @@ export function proxy(request: NextRequest) {
|
|||||||
return NextResponse.next();
|
return NextResponse.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for session cookie
|
// Check for session cookie on all other routes
|
||||||
const session = request.cookies.get('aaronai_session');
|
const session = request.cookies.get('aaronai_session');
|
||||||
if (!session?.value) {
|
if (!session?.value) {
|
||||||
return NextResponse.redirect(new URL('/login', request.url));
|
return NextResponse.redirect(new URL('/login', request.url));
|
||||||
|
|||||||
Reference in New Issue
Block a user