Skip to content

Commit 583af3f

Browse files
authored
Merge branch 'main' into yash/ocr-contracts-integration
2 parents 9729174 + a491de4 commit 583af3f

File tree

603 files changed

+11069
-8408
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

603 files changed

+11069
-8408
lines changed

.changeset/early-shoes-tap.md

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

.changeset/fallback-native-balance.md

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

apps/dashboard/redirects.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ async function redirects() {
448448
...legacyDashboardToTeamRedirects,
449449
...projectPageRedirects,
450450
...teamPageRedirects,
451+
{
452+
source: "/support/:path*",
453+
destination: "/team/~/~/support",
454+
permanent: false,
455+
},
451456
];
452457
}
453458

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"use server";
2+
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
3+
import type { SupportTicket } from "../../app/(app)/team/[team_slug]/(team)/~/support/types/tickets";
4+
import { getAuthToken, getAuthTokenWalletAddress } from "./auth-token";
5+
6+
const ESCALATION_FEEDBACK_RATING = 9999;
7+
8+
export async function createSupportTicket(params: {
9+
message: string;
10+
teamSlug: string;
11+
teamId: string;
12+
title: string;
13+
conversationId?: string;
14+
}): Promise<{ data: SupportTicket } | { error: string }> {
15+
const token = await getAuthToken();
16+
if (!token) {
17+
return { error: "No auth token available" };
18+
}
19+
20+
try {
21+
const walletAddress = await getAuthTokenWalletAddress();
22+
23+
const encodedTeamSlug = encodeURIComponent(params.teamSlug);
24+
const apiUrl = `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${encodedTeamSlug}/support-conversations`;
25+
26+
// Build the payload for creating a conversation
27+
// If the message does not already include wallet address, prepend it
28+
let message = params.message;
29+
if (!message.includes("Wallet address:")) {
30+
message = `Wallet address: ${String(walletAddress || "-")}\n${message}`;
31+
}
32+
33+
const payload = {
34+
markdown: message.trim(),
35+
title: params.title,
36+
};
37+
38+
const body = JSON.stringify(payload);
39+
const headers: Record<string, string> = {
40+
Accept: "application/json",
41+
Authorization: `Bearer ${token}`,
42+
"Content-Type": "application/json",
43+
"Accept-Encoding": "identity",
44+
};
45+
46+
const response = await fetch(apiUrl, {
47+
body,
48+
headers,
49+
method: "POST",
50+
});
51+
52+
if (!response.ok) {
53+
const errorText = await response.text();
54+
return { error: `API Server error: ${response.status} - ${errorText}` };
55+
}
56+
57+
const createdConversation: SupportTicket = await response.json();
58+
59+
// Escalate to SIWA feedback endpoint if conversationId is provided
60+
if (params.conversationId) {
61+
try {
62+
const siwaUrl = process.env.NEXT_PUBLIC_SIWA_URL;
63+
if (siwaUrl) {
64+
await fetch(`${siwaUrl}/v1/chat/feedback`, {
65+
method: "POST",
66+
headers: {
67+
"Content-Type": "application/json",
68+
Authorization: `Bearer ${token}`,
69+
...(params.teamId ? { "x-team-id": params.teamId } : {}),
70+
},
71+
body: JSON.stringify({
72+
conversationId: params.conversationId,
73+
feedbackRating: ESCALATION_FEEDBACK_RATING,
74+
}),
75+
});
76+
}
77+
} catch (error) {
78+
// Log error but don't fail the ticket creation
79+
console.error("Failed to escalate to SIWA feedback:", error);
80+
}
81+
}
82+
83+
return { data: createdConversation };
84+
} catch (error) {
85+
return {
86+
error: `Failed to create support ticket: ${error instanceof Error ? error.message : "Unknown error"}`,
87+
};
88+
}
89+
}
90+
91+
export async function sendMessageToTicket(request: {
92+
ticketId: string;
93+
teamSlug: string;
94+
teamId: string;
95+
message: string;
96+
}): Promise<{ success: true } | { error: string }> {
97+
if (!request.ticketId || !request.teamSlug) {
98+
return { error: "Ticket ID and team slug are required" };
99+
}
100+
101+
const token = await getAuthToken();
102+
if (!token) {
103+
return { error: "No auth token available" };
104+
}
105+
106+
try {
107+
const encodedTeamSlug = encodeURIComponent(request.teamSlug);
108+
const encodedTicketId = encodeURIComponent(request.ticketId);
109+
const apiUrl = `${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${encodedTeamSlug}/support-conversations/${encodedTicketId}/messages`;
110+
111+
// Append /unthread send for customer messages to ensure proper routing
112+
const messageWithUnthread = `${request.message.trim()}\n/unthread send`;
113+
const payload = {
114+
markdown: messageWithUnthread,
115+
};
116+
117+
const body = JSON.stringify(payload);
118+
const headers: Record<string, string> = {
119+
Accept: "application/json",
120+
Authorization: `Bearer ${token}`,
121+
"Content-Type": "application/json",
122+
"Accept-Encoding": "identity",
123+
...(request.teamId ? { "x-team-id": request.teamId } : {}),
124+
};
125+
126+
const response = await fetch(apiUrl, {
127+
body,
128+
headers,
129+
method: "POST",
130+
});
131+
132+
if (!response.ok) {
133+
const errorText = await response.text();
134+
return { error: `API Server error: ${response.status} - ${errorText}` };
135+
}
136+
137+
return { success: true };
138+
} catch (error) {
139+
return {
140+
error: `Failed to send message: ${error instanceof Error ? error.message : "Unknown error"}`,
141+
};
142+
}
143+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export async function service_getTeamBySlug(slug: string) {
4848
return null;
4949
}
5050

51-
export function getTeamById(id: string) {
51+
function getTeamById(id: string) {
5252
return getTeamBySlug(id);
5353
}
5454

apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { CrownIcon, LockIcon, SparklesIcon } from "lucide-react";
3+
import { CrownIcon, LockIcon } from "lucide-react";
44
import Link from "next/link";
55
import type React from "react";
66
import type { Team } from "@/api/team";
@@ -111,18 +111,15 @@ export function UpsellContent(props: {
111111
<CardContent className="space-y-6">
112112
{props.benefits && props.benefits.length > 0 && (
113113
<div className="space-y-3">
114-
<h4 className="font-semibold text-muted-foreground text-sm uppercase tracking-wide">
115-
What you'll get:
114+
<h4 className="font-semibold text-foreground text-sm capitalize text-center">
115+
What you'll get
116116
</h4>
117-
<div className="grid gap-2">
117+
<div className="grid gap-1.5">
118118
{props.benefits.map((benefit) => (
119119
<div
120-
className="flex items-center gap-3"
120+
className="flex items-center justify-center gap-3 text-center text-balance"
121121
key={benefit.description}
122122
>
123-
<div className="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-accent">
124-
<SparklesIcon className="h-3 w-3 text-success-text" />
125-
</div>
126123
<span className="text-sm">{benefit.description}</span>
127124
{benefit.status === "soon" && (
128125
<Badge className="text-xs" variant="secondary">

apps/dashboard/src/@/components/chat/ChatBar.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,12 @@ export function ChatBar(props: {
116116
) : (
117117
<Button
118118
aria-label="Send"
119-
className="!h-auto w-auto border border-nebula-pink-foreground p-2 disabled:opacity-100"
119+
className="!h-auto w-auto p-2 disabled:opacity-100"
120120
disabled={message.trim() === "" || props.isConnectingWallet}
121121
onClick={() => {
122122
if (message.trim() === "") return;
123123
handleSubmit(message);
124124
}}
125-
variant="pink"
126125
>
127126
<ArrowUpIcon className="size-4" />
128127
</Button>

apps/dashboard/src/@/components/chat/CustomChatButton.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"use client";
22

33
import { MessageCircleIcon, XIcon } from "lucide-react";
4+
import { useSelectedLayoutSegments } from "next/navigation";
45
import { useCallback, useRef, useState } from "react";
56
import { createThirdwebClient } from "thirdweb";
7+
import type { Team } from "@/api/team";
68
import { Button } from "@/components/ui/button";
79
import { NEXT_PUBLIC_DASHBOARD_CLIENT_ID } from "@/constants/public-envs";
810
import { cn } from "@/lib/utils";
@@ -14,22 +16,22 @@ const client = createThirdwebClient({
1416
});
1517

1618
export function CustomChatButton(props: {
17-
isLoggedIn: boolean;
18-
networks: "mainnet" | "testnet" | "all" | null;
19-
isFloating: boolean;
20-
pageType: "chain" | "contract" | "support";
2119
label: string;
2220
examplePrompts: string[];
23-
authToken: string | undefined;
24-
teamId: string | undefined;
21+
authToken: string;
22+
team: Team;
2523
clientId: string | undefined;
26-
requireLogin?: boolean;
2724
}) {
25+
const layoutSegments = useSelectedLayoutSegments();
2826
const [isOpen, setIsOpen] = useState(false);
2927
const [hasBeenOpened, setHasBeenOpened] = useState(false);
3028
const closeModal = useCallback(() => setIsOpen(false), []);
3129
const ref = useRef<HTMLDivElement>(null);
3230

31+
if (layoutSegments[0] === "~" && layoutSegments[1] === "support") {
32+
return null;
33+
}
34+
3335
return (
3436
<>
3537
{/* Inline Button (not floating) */}
@@ -54,14 +56,14 @@ export function CustomChatButton(props: {
5456
ref={ref}
5557
>
5658
{/* Header with close button */}
57-
<div className="flex items-center justify-between border-b px-4 py-2">
59+
<div className="flex items-center justify-between border-b px-4 py-4">
5860
<div className="flex items-center gap-2 font-semibold text-lg">
5961
<MessageCircleIcon className="size-5 text-muted-foreground" />
6062
{props.label}
6163
</div>
6264
<Button
6365
aria-label="Close chat"
64-
className="h-auto w-auto p-1 text-muted-foreground"
66+
className="h-auto w-auto p-1 text-muted-foreground rounded-full"
6567
onClick={closeModal}
6668
size="icon"
6769
variant="ghost"
@@ -80,9 +82,7 @@ export function CustomChatButton(props: {
8082
message: prompt,
8183
title: prompt,
8284
}))}
83-
networks={props.networks}
84-
requireLogin={props.requireLogin}
85-
teamId={props.teamId}
85+
team={props.team}
8686
/>
8787
)}
8888
</div>

0 commit comments

Comments
 (0)