Skip to content

Commit 9127fe7

Browse files
committed
Update AI chat UI
1 parent b862444 commit 9127fe7

File tree

6 files changed

+157
-111
lines changed

6 files changed

+157
-111
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/CreateSupportCase.tsx

Lines changed: 132 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
"use client";
22

3-
import { ArrowRightIcon } from "lucide-react";
3+
import { ArrowRightIcon, UserIcon } from "lucide-react";
44
import Link from "next/link";
55
import { useCallback, useState } from "react";
66
import type { Team } from "@/api/team";
7-
import { Reasoning } from "@/components/chat/Reasoning";
7+
import { MarkdownRenderer } from "@/components/blocks/markdown-renderer";
88
import { Button } from "@/components/ui/button";
9+
import { DynamicHeight } from "@/components/ui/DynamicHeight";
10+
import {
11+
Dialog,
12+
DialogContent,
13+
DialogHeader,
14+
DialogTitle,
15+
DialogTrigger,
16+
} from "@/components/ui/dialog";
17+
import { TextShimmer } from "@/components/ui/text-shimmer";
918
import { AutoResizeTextarea } from "@/components/ui/textarea";
10-
import { cn } from "../../../../../../../../@/lib/utils";
1119
import { ThirdwebMiniLogo } from "../../../../../../components/ThirdwebMiniLogo";
1220
import { SupportTicketForm } from "./SupportTicketForm";
1321

@@ -131,104 +139,122 @@ export function CreateSupportCase(props: { team: Team; authToken: string }) {
131139
);
132140

133141
const aiIcon = (
134-
<div className="rounded-full size-8 border bg-card shrink-0 flex items-center justify-center">
135-
<ThirdwebMiniLogo className="size-4 text-foreground" isMonoChrome />
142+
<div className="rounded-full size-9 border shrink-0 flex items-center justify-center bg-inverted">
143+
<ThirdwebMiniLogo
144+
className="size-4 text-inverted-foreground"
145+
isMonoChrome
146+
/>
147+
</div>
148+
);
149+
150+
const userIcon = (
151+
<div className="rounded-full size-9 border bg-muted/50 shrink-0 flex items-center justify-center">
152+
<UserIcon className="size-4 text-muted-foreground" />
136153
</div>
137154
);
138155

139156
return (
140-
<div className="flex flex-col grow bg-card border rounded-xl overflow-hidden">
157+
<div className="flex flex-col border bg-card rounded-xl mt-4">
158+
<div className="px-4 lg:px-12 py-6 lg:py-10 border-b border-dashed">
159+
<h2 className="text-xl lg:text-2xl font-semibold tracking-tight mb-0.5">
160+
Chat with support
161+
</h2>
162+
163+
<p className="text-muted-foreground text-sm md:text-base text-balance">
164+
Describe your issue and we'll help you resolve it
165+
</p>
166+
</div>
141167
{/* Chat Messages */}
142-
<div className="flex-1 overflow-y-auto space-y-10 pb-10 p-4">
143-
{chatMessages.map((message, index) => (
144-
<div
145-
key={message.id}
146-
className={cn(
147-
"flex",
148-
message.isUser ? "justify-end" : "justify-start",
149-
)}
150-
>
151-
<div
152-
className={cn(
153-
"max-w-[80%]",
154-
message.isUser &&
155-
"overflow-auto rounded-xl border bg-card px-4 py-2",
156-
)}
157-
>
168+
<DynamicHeight>
169+
<div className="flex-1 overflow-y-auto space-y-8 px-4 lg:px-12 pt-8 pb-20">
170+
{chatMessages.map((message, index) => (
171+
<div key={message.id}>
158172
{message.isUser ? (
159-
<p className="whitespace-pre-line text-sm md:text-base leading-loose md:leading-loose">
160-
{message.content}
161-
</p>
173+
<div className="flex items-start gap-3.5">
174+
{userIcon}
175+
<div className="px-3.5 py-2 rounded-xl border bg-muted/50 relative">
176+
<StyledMarkdownRenderer
177+
text={message.content}
178+
type="user"
179+
isMessagePending={false}
180+
/>
181+
</div>
182+
</div>
162183
) : message.content === "__reasoning__" ? (
163-
<div className="flex items-start gap-3">
184+
<div className="flex items-center gap-3.5">
164185
{aiIcon}
165-
<Reasoning isPending={true} texts={[]} />
186+
<TextShimmer
187+
text="Reasoning..."
188+
className="text-sm md:text-base"
189+
/>
166190
</div>
167191
) : (
168-
<div className="flex items-start gap-3">
192+
<div className="flex items-start gap-3.5">
169193
{aiIcon}
170194
<div>
171-
<p className="whitespace-pre-line leading-loose md:leading-loose text-sm md:text-base">
172-
{message.content}
173-
</p>
195+
<StyledMarkdownRenderer
196+
text={message.content}
197+
type="assistant"
198+
isMessagePending={false}
199+
/>
174200

175201
{/* Show Create Support Case button in the AI response - only if form not shown and after user interaction */}
176202
{index === chatMessages.length - 1 &&
177-
!showCreateForm &&
178203
!message.isSuccessMessage &&
179204
chatMessages.length > 2 && (
180-
<div className="mt-3">
181-
<Button
182-
onClick={() => setShowCreateForm(true)}
183-
size="sm"
184-
className="gap-2"
185-
>
186-
Create Support Case
187-
<ArrowRightIcon className="w-4 h-4" />
188-
</Button>
189-
</div>
190-
)}
191-
192-
{/* Show Support Case Form in the same message bubble when button is clicked */}
193-
{!message.isUser &&
194-
index === chatMessages.length - 1 &&
195-
showCreateForm &&
196-
message.content !== "__reasoning__" && (
197-
<div className="border p-4 rounded-lg bg-card mt-4">
198-
<h3 className="text-lg font-semibold text-foreground mb-0.5">
199-
Create Support Case
200-
</h3>
201-
<p className="text-sm text-muted-foreground mb-3">
202-
Let's create a detailed support case for our
203-
technical team.
204-
</p>
205+
<div className="mt-5">
206+
<Dialog>
207+
<DialogTrigger asChild>
208+
<Button
209+
size="sm"
210+
variant="outline"
211+
className="gap-2 rounded-full bg-background"
212+
>
213+
Create Support Case
214+
<ArrowRightIcon className="w-4 h-4" />
215+
</Button>
216+
</DialogTrigger>
217+
<DialogContent className="p-0 bg-card">
218+
<DynamicHeight>
219+
<DialogHeader className="p-4 lg:p-6 border-b border-dashed space-y-1">
220+
<DialogTitle className="text-xl font-semibold text-foreground tracking-tight">
221+
Create Support Case
222+
</DialogTitle>
223+
<p className="text-muted-foreground text-sm">
224+
Let's create a detailed support case for our
225+
technical team.
226+
</p>
227+
</DialogHeader>
205228

206-
<SupportTicketForm
207-
team={team}
208-
productLabel={productLabel}
209-
setProductLabel={setProductLabel}
210-
conversationId={conversationId}
211-
onSuccess={() => {
212-
setShowCreateForm(false);
213-
setProductLabel("");
214-
setChatMessages((prev) => [
215-
...prev,
216-
{
217-
id: Date.now(),
218-
content: `Support case created successfully!\n\nYour case has been submitted to our technical team. You'll receive updates via email at ${team.billingEmail}.\n\nYou can track your case in the support portal above.`,
219-
isUser: false,
220-
timestamp: new Date().toISOString(),
221-
isSuccessMessage: true,
222-
},
223-
]);
224-
}}
225-
/>
229+
<SupportTicketForm
230+
team={team}
231+
productLabel={productLabel}
232+
setProductLabel={setProductLabel}
233+
conversationId={conversationId}
234+
onSuccess={() => {
235+
setShowCreateForm(false);
236+
setProductLabel("");
237+
setChatMessages((prev) => [
238+
...prev,
239+
{
240+
id: Date.now(),
241+
content: `Support case created successfully!\n\nYour case has been submitted to our technical team. You'll receive updates via email at ${team.billingEmail}.\n\nYou can track your case in the support portal above.`,
242+
isUser: false,
243+
timestamp: new Date().toISOString(),
244+
isSuccessMessage: true,
245+
},
246+
]);
247+
}}
248+
/>
249+
</DynamicHeight>
250+
</DialogContent>
251+
</Dialog>
226252
</div>
227253
)}
228254

229255
{/* Show Back to Support button for success message */}
230256
{!message.isUser && message.isSuccessMessage && (
231-
<div className="mt-3">
257+
<div className="mt-5">
232258
<Button asChild size="sm" className="gap-2">
233259
<Link href={`/team/${team.slug}/~/support`}>
234260
View all cases
@@ -241,20 +267,20 @@ export function CreateSupportCase(props: { team: Team; authToken: string }) {
241267
</div>
242268
)}
243269
</div>
244-
</div>
245-
))}
246-
</div>
270+
))}
271+
</div>
272+
</DynamicHeight>
247273

248274
{/* Chat Input */}
249275
{!showCreateForm && (
250-
<div className="border-t">
276+
<div className="px-4 lg:px-12 pb-12">
251277
<div className="relative">
252278
<AutoResizeTextarea
253279
placeholder="I am having an issue with..."
254280
value={chatInput}
255281
onChange={(e) => setChatInput(e.target.value)}
256282
onKeyDown={handleChatKeyPress}
257-
className="min-h-[120px] border-none"
283+
className="min-h-[120px] rounded-xl"
258284
/>
259285
<Button
260286
onClick={handleChatSend}
@@ -271,3 +297,26 @@ export function CreateSupportCase(props: { team: Team; authToken: string }) {
271297
</div>
272298
);
273299
}
300+
301+
function StyledMarkdownRenderer(props: {
302+
text: string;
303+
isMessagePending: boolean;
304+
type: "assistant" | "user";
305+
}) {
306+
return (
307+
<MarkdownRenderer
308+
className="text-sm md:text-base text-foreground [&>*:first-child]:mt-0 [&>*:first-child]:border-none [&>*:first-child]:pb-0 [&>*:last-child]:mb-0 leading-relaxed"
309+
code={{
310+
className: "bg-transparent",
311+
ignoreFormattingErrors: true,
312+
}}
313+
inlineCode={{ className: "border-none" }}
314+
li={{ className: "text-foreground leading-relaxed" }}
315+
markdownText={props.text}
316+
p={{
317+
className: "text-foreground leading-relaxed",
318+
}}
319+
skipHtml
320+
/>
321+
);
322+
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportTabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export function SupportTabs({
4242
</div>
4343

4444
{/* Tab Buttons */}
45-
<div className="grid w-full grid-cols-3 bg-background border border-border p-1 rounded-lg sm:w-fit gap-0.5">
45+
<div className="grid w-full grid-cols-3 border bg-card p-1 rounded-lg sm:w-fit gap-0.5">
4646
{tabs.map((tab) => (
4747
<Button
4848
className={cn(

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/SupportTicketForm.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { LoaderCircleIcon } from "lucide-react";
21
import dynamic from "next/dynamic";
32
import { useRef, useState } from "react";
43
import { toast } from "sonner";
54
import { revalidatePathAction } from "@/actions/revalidate";
65
import { createSupportTicket } from "@/api/support";
76
import type { Team } from "@/api/team";
87
import { Button } from "@/components/ui/button";
8+
import { Spinner } from "@/components/ui/Spinner/Spinner";
99
import { Skeleton } from "@/components/ui/skeleton";
1010
import { SupportForm_SelectInput } from "./shared/SupportForm_SelectInput";
1111

@@ -192,17 +192,23 @@ export function SupportTicketForm({
192192

193193
return (
194194
<form onSubmit={handleFormSubmit} ref={formRef} className="space-y-4">
195-
<ProductAreaSelection
196-
productLabel={productLabel}
197-
setProductLabel={setProductLabel}
198-
/>
195+
<div className="p-4 lg:p-6">
196+
<ProductAreaSelection
197+
productLabel={productLabel}
198+
setProductLabel={setProductLabel}
199+
/>
200+
</div>
199201
{/* Submit Buttons */}
200-
<div className="flex justify-end gap-3 pt-2">
201-
<Button type="submit" size="sm" disabled={isSubmittingForm}>
202+
<div className="flex justify-end gap-3 p-4 lg:p-6 border-t border-dashed">
203+
<Button
204+
type="submit"
205+
disabled={isSubmittingForm}
206+
className="gap-2 disabled:opacity-100"
207+
>
202208
{isSubmittingForm ? (
203209
<>
204-
<LoaderCircleIcon className="animate-spin w-4 h-4 mr-2 inline" />
205-
Creating...
210+
<Spinner className="size-4" />
211+
Creating Support Case
206212
</>
207213
) : (
208214
"Create Support Case"

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/shared/SupportForm_DescriptionInput.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ interface Props {
1515
export function DescriptionInput(props: Props) {
1616
return (
1717
<div className={cn("flex flex-col gap-2", props.className)}>
18-
<Label
19-
className="text-base font-medium text-foreground"
20-
htmlFor="markdown"
21-
>
18+
<Label className="font-medium text-foreground" htmlFor="markdown">
2219
Description
23-
<span className="text-red-500 ml-1"></span>
20+
<span className="text-red-500 ml-1 align-middle">*</span>
2421
</Label>
2522

2623
<Textarea

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/shared/SupportForm_SelectInput.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@ export const SupportForm_SelectInput = (props: Props) => {
2121
const { options, formLabel, name, required, promptText } = props;
2222

2323
return (
24-
<div className="flex flex-col items-start gap-3">
25-
<Label
26-
className="relative text-base font-medium text-foreground"
27-
htmlFor={name}
28-
>
24+
<div className="flex flex-col items-start gap-2">
25+
<Label className="relative font-medium text-foreground" htmlFor={name}>
2926
{formLabel}
30-
{required && (
31-
<span className="-top-1.5 -right-2 absolute text-red-500"></span>
32-
)}
27+
{required && <span className="text-red-500 ml-1 align-middle">*</span>}
3328
</Label>
3429

3530
<Select

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/support/_components/shared/SupportForm_TextInput.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { HTMLInputTypeAttribute } from "react";
22
import { Input } from "@/components/ui/input";
33
import { Label } from "@/components/ui/label";
4+
import { cn } from "@/lib/utils";
45

56
type Props = {
67
formLabel: string;
@@ -15,15 +16,13 @@ export const SupportForm_TextInput = (props: Props) => {
1516
const { formLabel, formValue, required, placeholder, inputType, className } =
1617
props;
1718
return (
18-
<div className={`flex flex-col items-start gap-3 ${className || ""}`}>
19+
<div className={cn("flex flex-col items-start gap-2", className)}>
1920
<Label
20-
className="relative text-base font-medium text-foreground"
21+
className="relative font-medium text-foreground"
2122
htmlFor={formValue}
2223
>
2324
{formLabel}
24-
{required && (
25-
<span className="-top-1.5 -right-2 absolute text-red-500"></span>
26-
)}
25+
{required && <span className="text-red-500 ml-1 align-middle">*</span>}
2726
</Label>
2827

2928
<Input

0 commit comments

Comments
 (0)