Skip to content

Commit 5bf8b89

Browse files
committed
PEER-236: UI changes
1 parent b992011 commit 5bf8b89

File tree

5 files changed

+116
-176
lines changed

5 files changed

+116
-176
lines changed

frontend/src/components/blocks/interview/chat/chat-layout.tsx

Lines changed: 53 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Loader2, Maximize2, MessageSquare, Minimize2, Send, X } from 'lucide-react';
1+
import { Loader2, MessageSquare, Send, X } from 'lucide-react';
22
import React, { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from 'react';
33

44
import { Alert, AlertDescription } from '@/components/ui/alert';
@@ -28,7 +28,6 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
2828
title,
2929
}) => {
3030
const [input, setInput] = useState<string>('');
31-
const [isExpanded, setIsExpanded] = useState<boolean>(false);
3231
const inputRef = useRef<HTMLInputElement>(null);
3332
const messagesEndRef = useRef<HTMLDivElement>(null);
3433

@@ -62,85 +61,62 @@ export const ChatLayout: React.FC<ChatLayoutProps> = ({
6261
};
6362

6463
return (
65-
<div
66-
className={`fixed right-0 top-14 h-[calc(100%-3.5rem)] bg-white shadow-xl transition-all duration-300 ease-in-out ${
67-
isOpen ? 'translate-x-0' : 'translate-x-full'
68-
} ${isExpanded ? 'w-3/4' : 'w-1/5'}`}
69-
>
70-
<div className='flex h-full flex-col'>
71-
<div className='flex items-center justify-between border-b bg-white px-4 py-3'>
72-
<div className='flex items-center gap-2'>
73-
<MessageSquare className='size-5 text-blue-500' />
74-
<h2 className='text-base font-semibold'>{title}</h2>
75-
</div>
76-
<div className='flex items-center gap-2'>
77-
<Button
78-
variant='ghost'
79-
size='icon'
80-
onClick={() => setIsExpanded(!isExpanded)}
81-
className='rounded-full hover:bg-gray-100'
82-
>
83-
{isExpanded ? <Minimize2 className='size-5' /> : <Maximize2 className='size-5' />}
84-
</Button>
85-
<Button
86-
variant='ghost'
87-
size='icon'
88-
onClick={onClose}
89-
className='rounded-full hover:bg-gray-100'
90-
>
91-
<X className='size-5' />
92-
</Button>
93-
</div>
64+
<div className='flex size-full flex-col'>
65+
<div className='bg-secondary/50 flex items-center justify-between border-b px-4 py-3'>
66+
<div className='flex items-center gap-2'>
67+
<h2 className='font-semibold'>{title}</h2>
9468
</div>
69+
<div className='flex items-center gap-2'>
70+
<Button
71+
variant='ghost'
72+
size='icon'
73+
onClick={onClose}
74+
className='rounded-full hover:bg-gray-100'
75+
>
76+
<X className='size-5' />
77+
</Button>
78+
</div>
79+
</div>
9580

96-
<ScrollArea className='flex-1 overflow-y-auto p-4'>
97-
{messages.length === 0 && (
98-
<div className='flex h-full flex-col items-center justify-center text-gray-500'>
99-
<MessageSquare className='mb-4 size-12 opacity-50' />
100-
<p className='text-center'>No messages yet. Start a conversation!</p>
101-
</div>
102-
)}
103-
{messages.map((msg, index) => (
104-
<ChatMessage key={index} message={msg} />
105-
))}
106-
{isLoading && (
107-
<div className='mb-4 flex justify-start'>
108-
<div className='rounded-lg bg-gray-50 px-4 py-2'>
109-
<Loader2 className='size-5 animate-spin text-gray-500' />
110-
</div>
81+
<ScrollArea className='h-full flex-1 overflow-y-auto p-4'>
82+
{messages.length === 0 && (
83+
<div className='flex h-full flex-col items-center justify-center text-gray-500'>
84+
<MessageSquare className='mb-4 size-12 opacity-50' />
85+
<p className='text-center'>No messages yet. Start a conversation!</p>
86+
</div>
87+
)}
88+
{messages.map((msg, index) => (
89+
<ChatMessage key={index} message={msg} />
90+
))}
91+
{isLoading && (
92+
<div className='mb-4 flex justify-start'>
93+
<div className='rounded-lg bg-gray-50 px-4 py-2'>
94+
<Loader2 className='size-5 animate-spin text-gray-500' />
11195
</div>
112-
)}
113-
{error && (
114-
<Alert variant='destructive' className='mb-4'>
115-
<AlertDescription>{error}</AlertDescription>
116-
</Alert>
117-
)}
118-
<div ref={messagesEndRef} />
119-
</ScrollArea>
120-
121-
<div className='border-t bg-white p-4'>
122-
<div className='flex gap-2'>
123-
<Input
124-
ref={inputRef}
125-
value={input}
126-
onChange={(e: ChangeEvent<HTMLInputElement>) => setInput(e.target.value)}
127-
placeholder='Type your message...'
128-
onKeyPress={handleKeyPress}
129-
disabled={isLoading}
130-
className='flex-1'
131-
/>
132-
<Button
133-
onClick={handleSend}
134-
disabled={isLoading || !input.trim()}
135-
className='bg-blue-500 hover:bg-blue-600'
136-
>
137-
{isLoading ? (
138-
<Loader2 className='size-4 animate-spin' />
139-
) : (
140-
<Send className='size-4' />
141-
)}
142-
</Button>
14396
</div>
97+
)}
98+
{error && (
99+
<Alert variant='destructive' className='mb-4'>
100+
<AlertDescription>{error}</AlertDescription>
101+
</Alert>
102+
)}
103+
<div ref={messagesEndRef} />
104+
</ScrollArea>
105+
106+
<div className='bg-secondary/50 border p-4'>
107+
<div className='flex gap-2'>
108+
<Input
109+
ref={inputRef}
110+
value={input}
111+
onChange={(e: ChangeEvent<HTMLInputElement>) => setInput(e.target.value)}
112+
placeholder='Type your message...'
113+
onKeyPress={handleKeyPress}
114+
disabled={isLoading}
115+
className='bg-primary-foreground flex-1'
116+
/>
117+
<Button variant='outline' onClick={handleSend} disabled={isLoading || !input.trim()}>
118+
{isLoading ? <Loader2 className='size-4 animate-spin' /> : <Send className='size-4' />}
119+
</Button>
144120
</div>
145121
</div>
146122
</div>

frontend/src/components/blocks/interview/chat/chat-message.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ interface ChatMessageProps {
1111

1212
const CodeBlock: React.FC<{ content: string }> = ({ content }) => (
1313
<div className='group relative'>
14-
<pre className='my-4 overflow-x-auto rounded-md bg-gray-900 p-4 text-sm text-gray-100'>
14+
<pre className='bg-secondary my-4 overflow-x-auto rounded-md p-4 text-sm'>
1515
<code>{content}</code>
1616
</pre>
1717
<button
1818
onClick={() => navigator.clipboard.writeText(content)}
19-
className='absolute right-2 top-2 rounded bg-gray-700 px-2 py-1 text-xs text-gray-300 opacity-0 transition-opacity group-hover:opacity-100'
19+
className='bg-secondary/80 absolute right-2 top-2 rounded px-2 py-1 text-xs opacity-0 transition-opacity group-hover:opacity-100'
2020
>
2121
Copy
2222
</button>
@@ -32,7 +32,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message }) => {
3232
<div className={`flex ${message.isUser ? 'justify-end' : 'justify-start'} mb-4`}>
3333
<div
3434
className={`max-w-[85%] rounded-lg px-4 py-2 text-xs ${
35-
message.isUser ? 'bg-blue-500 text-white' : 'border border-gray-100 bg-gray-50'
35+
message.isUser ? 'bg-primary-foreground' : 'bg-secondary'
3636
}`}
3737
>
3838
{parts.map((part, index) => {

frontend/src/components/blocks/interview/editor.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ChevronLeftIcon } from '@radix-ui/react-icons';
22
import { useWindowSize } from '@uidotdev/usehooks';
33
import type { LanguageName } from '@uiw/codemirror-extensions-langs';
44
import CodeMirror from '@uiw/react-codemirror';
5+
import { Bot, User } from 'lucide-react';
56
import { useMemo, useState } from 'react';
67

78
import { Button } from '@/components/ui/button';
@@ -22,9 +23,11 @@ const MIN_EDITOR_HEIGHT = 350;
2223

2324
type EditorProps = {
2425
room: string;
26+
onAIClick: () => void;
27+
onPartnerClick: () => void;
2528
};
2629

27-
export const Editor = ({ room }: EditorProps) => {
30+
export const Editor = ({ room, onAIClick, onPartnerClick }: EditorProps) => {
2831
const { height } = useWindowSize();
2932
const [theme, setTheme] = useState<IEditorTheme>('vscodeDark');
3033
const {
@@ -104,6 +107,24 @@ export const Editor = ({ room }: EditorProps) => {
104107
</div>
105108
))}
106109
</div>
110+
<Button
111+
variant='outline'
112+
size='sm'
113+
className='group flex items-center !px-2 !py-1'
114+
onClick={onAIClick}
115+
>
116+
<Bot className='mr-1 size-4' />
117+
<span>AI Assistant</span>
118+
</Button>
119+
<Button
120+
variant='outline'
121+
size='sm'
122+
className='group flex items-center !px-2 !py-1'
123+
onClick={onPartnerClick}
124+
>
125+
<User className='mr-1 size-4' />
126+
<span>Chat</span>
127+
</Button>
107128
<Button variant='destructive' size='sm' className='group flex items-center !px-2 !py-1'>
108129
<ChevronLeftIcon className='transition-transform duration-200 ease-in-out group-hover:-translate-x-px' />
109130
<span>Disconnect</span>

frontend/src/components/blocks/interview/floating-chat-button.tsx

Lines changed: 0 additions & 82 deletions
This file was deleted.

frontend/src/routes/interview/[room]/main.tsx

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { useMemo, useState } from 'react';
33
import { type LoaderFunctionArgs, Navigate, useLoaderData } from 'react-router-dom';
44

55
import { WithNavBanner, WithNavBlocker } from '@/components/blocks/authed';
6+
import { AIChat } from '@/components/blocks/interview/ai-chat';
67
import { Editor } from '@/components/blocks/interview/editor';
7-
import { FloatingChatButton } from '@/components/blocks/interview/floating-chat-button';
8+
import { PartnerChat } from '@/components/blocks/interview/partner-chat';
89
import { QuestionDetails } from '@/components/blocks/questions/details';
910
import { Card } from '@/components/ui/card';
1011
import { useCrumbs } from '@/lib/hooks';
@@ -29,25 +30,49 @@ export const InterviewRoom = () => {
2930
const { crumbs } = useCrumbs();
3031
const { data: details } = useSuspenseQuery(questionDetailsQuery(questionId));
3132
const questionDetails = useMemo(() => details.question, [details]);
32-
const [isChatOpen, setIsChatOpen] = useState(false);
33+
const [isAIChatOpen, setIsAIChatOpen] = useState(false);
34+
const [isPartnerChatOpen, setIsPartnerChatOpen] = useState(false);
35+
36+
const handleAIClick = () => {
37+
setIsPartnerChatOpen(false);
38+
setIsAIChatOpen(!isAIChatOpen);
39+
};
40+
41+
const handlePartnerClick = () => {
42+
setIsAIChatOpen(false);
43+
setIsPartnerChatOpen(!isPartnerChatOpen);
44+
};
3345

3446
return !questionId || !roomId ? (
3547
<Navigate to={ROUTES.HOME} />
3648
) : (
3749
<WithNavBlocker>
3850
<WithNavBanner crumbs={crumbs}>
39-
<div className='flex'>
40-
<div className={`flex ${isChatOpen ? 'w-4/5' : 'w-full'} transition-all duration-300`}>
41-
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
42-
<QuestionDetails {...{ questionDetails }} />
43-
</Card>
44-
<div
45-
className={`flex flex-1 ${isChatOpen ? 'w-2/3' : 'w-full'} transition-all duration-300`}
46-
>
47-
<Editor room={roomId as string} />
48-
</div>
51+
<div className='flex flex-1 overflow-hidden'>
52+
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
53+
<QuestionDetails {...{ questionDetails }} />
54+
</Card>
55+
<div className='flex w-full'>
56+
<Editor
57+
room={roomId as string}
58+
onAIClick={handleAIClick}
59+
onPartnerClick={handlePartnerClick}
60+
/>
4961
</div>
50-
<FloatingChatButton room={roomId as string} onChatOpenChange={setIsChatOpen} />
62+
{(isAIChatOpen || isPartnerChatOpen) && (
63+
<Card className='border-border m-4 w-1/3 overflow-hidden'>
64+
{isAIChatOpen && (
65+
<AIChat isOpen={isAIChatOpen} onClose={() => setIsAIChatOpen(false)} />
66+
)}
67+
{isPartnerChatOpen && (
68+
<PartnerChat
69+
roomId={roomId as string}
70+
isOpen={isPartnerChatOpen}
71+
onClose={() => setIsPartnerChatOpen(false)}
72+
/>
73+
)}
74+
</Card>
75+
)}
5176
</div>
5277
</WithNavBanner>
5378
</WithNavBlocker>

0 commit comments

Comments
 (0)