Skip to content

Commit aa1b190

Browse files
committed
layout cleanup
1 parent e6cadf9 commit aa1b190

File tree

9 files changed

+242
-250
lines changed

9 files changed

+242
-250
lines changed

apps/dashboard/src/@/api/support.ts

Lines changed: 4 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
11
"use server";
2-
import "server-only";
32
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
3+
import type {
4+
SupportMessage,
5+
SupportTicket,
6+
} from "../../app/(app)/team/[team_slug]/(team)/~/support/types/tickets";
47
import { getAuthToken, getAuthTokenWalletAddress } from "./auth-token";
58

6-
export interface SupportTicket {
7-
id: string;
8-
status: "needs_response" | "in_progress" | "on_hold" | "closed" | "resolved";
9-
createdAt: string;
10-
updatedAt: string;
11-
messages?: SupportMessage[];
12-
}
13-
14-
interface SupportMessage {
15-
id: string;
16-
content: string;
17-
createdAt: string;
18-
timestamp: string;
19-
author?: {
20-
name: string;
21-
email: string;
22-
type: "user" | "customer";
23-
};
24-
}
25-
269
interface CreateSupportTicketRequest {
2710
message: string;
2811
teamSlug: string;
@@ -38,57 +21,6 @@ interface SendMessageRequest {
3821
message: string;
3922
}
4023

41-
export async function getSupportTicketsByTeam(
42-
teamSlug: string,
43-
authToken?: string,
44-
): Promise<{ data: SupportTicket[] } | { error: string }> {
45-
if (!teamSlug) {
46-
return { error: "Team slug is required to fetch support tickets" };
47-
}
48-
49-
const token = authToken || (await getAuthToken());
50-
if (!token) {
51-
return { error: "No auth token available" };
52-
}
53-
54-
// URL encode the team slug to handle special characters like #
55-
const encodedTeamSlug = encodeURIComponent(teamSlug);
56-
const apiUrl = `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${encodedTeamSlug}/support-conversations/list`;
57-
58-
// Build the POST payload according to API spec
59-
const payload = {
60-
limit: 50,
61-
descending: true,
62-
};
63-
64-
try {
65-
const response = await fetch(apiUrl, {
66-
body: JSON.stringify(payload),
67-
cache: "no-store",
68-
headers: {
69-
Accept: "application/json",
70-
"Accept-Encoding": "identity",
71-
Authorization: `Bearer ${token}`,
72-
"Content-Type": "application/json",
73-
},
74-
method: "POST",
75-
});
76-
77-
if (!response.ok) {
78-
const errorText = await response.text();
79-
return { error: `API Server error: ${response.status} - ${errorText}` };
80-
}
81-
82-
const data: { data?: SupportTicket[] } = await response.json();
83-
const conversations = data.data || [];
84-
return { data: conversations };
85-
} catch (error) {
86-
return {
87-
error: `Failed to fetch support tickets: ${error instanceof Error ? error.message : "Unknown error"}`,
88-
};
89-
}
90-
}
91-
9224
interface RawSupportMessage {
9325
id: string;
9426
text?: string;

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

Lines changed: 135 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -212,159 +212,151 @@ export function CreateSupportCase({ team, authToken }: CreateSupportCaseProps) {
212212
}, [_handleStartChat]);
213213

214214
return (
215-
<div className="flex flex-col gap-4 py-6 pb-20 px-4 sm:px-6 lg:px-8">
216-
<div className="max-w-4xl mx-auto">
217-
<div className="border border-border bg-card rounded-lg h-[800px] flex flex-col">
218-
{/* Chat Header */}
219-
<div className="flex items-center gap-3 p-4 border-b border-border">
220-
<div className="relative bg-primary/10 p-2 rounded-full">
221-
<BotIcon className="w-5 h-5 text-primary" />
222-
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-card"></div>
223-
</div>
224-
<div>
225-
<h3 className="text-foreground font-medium">
226-
Ask AI for support
227-
</h3>
228-
<p className="text-xs text-muted-foreground">Online</p>
229-
</div>
230-
</div>
215+
<div className="border border-border bg-card rounded-lg h-[800px] flex flex-col">
216+
{/* Chat Header */}
217+
<div className="flex items-center gap-3 p-4 border-b border-border">
218+
<div className="relative bg-primary/10 p-2 rounded-full">
219+
<BotIcon className="w-5 h-5 text-primary" />
220+
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-card"></div>
221+
</div>
222+
<div>
223+
<h3 className="text-foreground font-medium">Ask AI for support</h3>
224+
<p className="text-xs text-muted-foreground">Online</p>
225+
</div>
226+
</div>
231227

232-
{/* Chat Messages */}
228+
{/* Chat Messages */}
229+
<div
230+
className="flex-1 p-6 overflow-y-auto space-y-4"
231+
ref={chatContainerRef}
232+
>
233+
{chatMessages.map((message, index) => (
233234
<div
234-
className="flex-1 p-6 overflow-y-auto space-y-4"
235-
ref={chatContainerRef}
235+
key={message.id}
236+
className={`flex ${message.isUser ? "justify-end" : "justify-start"}`}
236237
>
237-
{chatMessages.map((message, index) => (
238-
<div
239-
key={message.id}
240-
className={`flex ${message.isUser ? "justify-end" : "justify-start"}`}
241-
>
242-
<div
243-
className={`max-w-[80%] p-4 rounded-lg ${
244-
message.isUser
245-
? "bg-primary text-primary-foreground"
246-
: "bg-muted text-foreground border border-border"
247-
}`}
248-
>
249-
{message.isUser ? (
250-
<p className="text-sm whitespace-pre-line">
251-
{message.content}
252-
</p>
253-
) : message.content === "__reasoning__" ? (
254-
<Reasoning isPending={true} texts={[]} />
255-
) : (
256-
<MarkdownRenderer
257-
className="text-sm"
258-
li={{ className: "text-foreground" }}
259-
p={{ className: "text-foreground" }}
260-
markdownText={message.content}
261-
/>
262-
)}
263-
<p className="text-xs opacity-70 mt-2">
264-
{new Date(message.timestamp).toLocaleTimeString()}
265-
</p>
238+
<div
239+
className={`max-w-[80%] p-4 rounded-lg ${
240+
message.isUser
241+
? "bg-primary text-primary-foreground"
242+
: "bg-muted text-foreground border border-border"
243+
}`}
244+
>
245+
{message.isUser ? (
246+
<p className="text-sm whitespace-pre-line">{message.content}</p>
247+
) : message.content === "__reasoning__" ? (
248+
<Reasoning isPending={true} texts={[]} />
249+
) : (
250+
<MarkdownRenderer
251+
className="text-sm"
252+
li={{ className: "text-foreground" }}
253+
p={{ className: "text-foreground" }}
254+
markdownText={message.content}
255+
/>
256+
)}
257+
<p className="text-xs opacity-70 mt-2">
258+
{new Date(message.timestamp).toLocaleTimeString()}
259+
</p>
266260

267-
{/* Show Back to Support button for success message */}
268-
{!message.isUser && message.isSuccessMessage && (
269-
<div className="mt-3 pt-3 border-t border-border">
270-
<Button
271-
onClick={handleBackToSupport}
272-
size="sm"
273-
className="bg-primary hover:bg-primary/80 text-primary-foreground transition-opacity"
274-
>
275-
Back to Support
276-
</Button>
277-
</div>
278-
)}
261+
{/* Show Back to Support button for success message */}
262+
{!message.isUser && message.isSuccessMessage && (
263+
<div className="mt-3 pt-3 border-t border-border">
264+
<Button
265+
onClick={handleBackToSupport}
266+
size="sm"
267+
className="bg-primary hover:bg-primary/80 text-primary-foreground transition-opacity"
268+
>
269+
Back to Support
270+
</Button>
271+
</div>
272+
)}
279273

280-
{/* Show Create Support Case button in the AI response - only if form not shown and after user interaction */}
281-
{!message.isUser &&
282-
index === chatMessages.length - 1 &&
283-
!showCreateForm &&
284-
!message.isSuccessMessage &&
285-
message.content !== "__reasoning__" &&
286-
chatMessages.length > 2 && (
287-
<div className="mt-3 pt-3 border-t border-border">
288-
<Button
289-
onClick={() => setShowCreateForm(true)}
290-
size="sm"
291-
className="bg-primary hover:bg-primary/80 text-primary-foreground transition-opacity"
292-
>
293-
Create Support Case
294-
</Button>
295-
</div>
296-
)}
274+
{/* Show Create Support Case button in the AI response - only if form not shown and after user interaction */}
275+
{!message.isUser &&
276+
index === chatMessages.length - 1 &&
277+
!showCreateForm &&
278+
!message.isSuccessMessage &&
279+
message.content !== "__reasoning__" &&
280+
chatMessages.length > 2 && (
281+
<div className="mt-3 pt-3 border-t border-border">
282+
<Button
283+
onClick={() => setShowCreateForm(true)}
284+
size="sm"
285+
className="bg-primary hover:bg-primary/80 text-primary-foreground transition-opacity"
286+
>
287+
Create Support Case
288+
</Button>
289+
</div>
290+
)}
297291

298-
{/* Show Support Case Form in the same message bubble when button is clicked */}
299-
{!message.isUser &&
300-
index === chatMessages.length - 1 &&
301-
showCreateForm &&
302-
message.content !== "__reasoning__" && (
303-
<div className="mt-4 pt-4 border-t border-border">
304-
<div className="mb-4">
305-
<h3 className="text-lg font-semibold text-foreground mb-2">
306-
Create Support Case
307-
</h3>
308-
<p className="text-sm text-muted-foreground">
309-
Let's create a detailed support case for our
310-
technical team.
311-
</p>
312-
</div>
292+
{/* Show Support Case Form in the same message bubble when button is clicked */}
293+
{!message.isUser &&
294+
index === chatMessages.length - 1 &&
295+
showCreateForm &&
296+
message.content !== "__reasoning__" && (
297+
<div className="mt-4 pt-4 border-t border-border">
298+
<div className="mb-4">
299+
<h3 className="text-lg font-semibold text-foreground mb-2">
300+
Create Support Case
301+
</h3>
302+
<p className="text-sm text-muted-foreground">
303+
Let's create a detailed support case for our technical
304+
team.
305+
</p>
306+
</div>
313307

314-
<SupportTicketForm
315-
team={team}
316-
productLabel={productLabel}
317-
setProductLabel={setProductLabel}
318-
conversationId={conversationId}
319-
onSuccess={() => {
320-
setShowCreateForm(false);
321-
setProductLabel("");
322-
setChatMessages((prev) => [
323-
...prev,
324-
{
325-
id: Date.now(),
326-
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.`,
327-
isUser: false,
328-
timestamp: new Date().toISOString(),
329-
isSuccessMessage: true,
330-
},
331-
]);
332-
}}
333-
/>
334-
</div>
335-
)}
336-
</div>
337-
</div>
338-
))}
339-
<div ref={messagesEndRef} />
308+
<SupportTicketForm
309+
team={team}
310+
productLabel={productLabel}
311+
setProductLabel={setProductLabel}
312+
conversationId={conversationId}
313+
onSuccess={() => {
314+
setShowCreateForm(false);
315+
setProductLabel("");
316+
setChatMessages((prev) => [
317+
...prev,
318+
{
319+
id: Date.now(),
320+
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.`,
321+
isUser: false,
322+
timestamp: new Date().toISOString(),
323+
isSuccessMessage: true,
324+
},
325+
]);
326+
}}
327+
/>
328+
</div>
329+
)}
330+
</div>
340331
</div>
332+
))}
333+
<div ref={messagesEndRef} />
334+
</div>
341335

342-
{/* Chat Input */}
343-
{!showCreateForm && (
344-
<div className="p-6 border-t border-border">
345-
<div className="relative">
346-
<Textarea
347-
placeholder="Type your message here..."
348-
value={chatInput}
349-
onChange={(e) => setChatInput(e.target.value)}
350-
onKeyDown={handleChatKeyPress}
351-
className="flex-1 min-h-[120px] max-h-[300px] bg-background border-border text-foreground placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-primary resize-y pr-12"
352-
/>
353-
<Button
354-
onClick={handleChatSend}
355-
disabled={!chatInput.trim()}
356-
className="absolute bottom-3 right-3 bg-primary hover:bg-primary/90 text-primary-foreground disabled:opacity-50 disabled:cursor-not-allowed h-8 w-8 p-0"
357-
>
358-
<SendIcon className="w-4 h-4" />
359-
</Button>
360-
</div>
361-
<p className="text-xs text-muted-foreground mt-2">
362-
Press Enter to send, Shift+Enter for new line
363-
</p>
364-
</div>
365-
)}
336+
{/* Chat Input */}
337+
{!showCreateForm && (
338+
<div className="p-6 border-t border-border">
339+
<div className="relative">
340+
<Textarea
341+
placeholder="Type your message here..."
342+
value={chatInput}
343+
onChange={(e) => setChatInput(e.target.value)}
344+
onKeyDown={handleChatKeyPress}
345+
className="flex-1 min-h-[120px] max-h-[300px] bg-background border-border text-foreground placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:border-primary resize-y pr-12"
346+
/>
347+
<Button
348+
onClick={handleChatSend}
349+
disabled={!chatInput.trim()}
350+
className="absolute bottom-3 right-3 bg-primary hover:bg-primary/90 text-primary-foreground disabled:opacity-50 disabled:cursor-not-allowed h-8 w-8 p-0"
351+
>
352+
<SendIcon className="w-4 h-4" />
353+
</Button>
354+
</div>
355+
<p className="text-xs text-muted-foreground mt-2">
356+
Press Enter to send, Shift+Enter for new line
357+
</p>
366358
</div>
367-
</div>
359+
)}
368360
</div>
369361
);
370362
}

0 commit comments

Comments
 (0)