-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathroute.tsx
More file actions
124 lines (108 loc) · 3.87 KB
/
Copy pathroute.tsx
File metadata and controls
124 lines (108 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import { CheckCircleIcon } from "@heroicons/react/24/solid";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
import { Callout } from "~/components/primitives/Callout";
import { Header1 } from "~/components/primitives/Headers";
import { Icon } from "~/components/primitives/Icon";
import { Paragraph } from "~/components/primitives/Paragraph";
import { logger } from "~/services/logger.server";
import { createPersonalAccessTokenFromAuthorizationCode } from "~/services/personalAccessToken.server";
import { requireUserId } from "~/services/session.server";
const ParamsSchema = z.object({
authorizationCode: z.string(),
});
const SearchParamsSchema = z.object({
source: z.string().optional(),
clientName: z.string().optional(),
});
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
const parsedParams = ParamsSchema.safeParse(params);
if (!parsedParams.success) {
logger.info("Invalid params", { params });
throw new Response(undefined, {
status: 400,
statusText: "Invalid params",
});
}
const url = new URL(request.url);
const searchObject = Object.fromEntries(url.searchParams.entries());
const searchParams = SearchParamsSchema.safeParse(searchObject);
const source = (searchParams.success ? searchParams.data.source : undefined) ?? "cli";
const clientName = (searchParams.success ? searchParams.data.clientName : undefined) ?? "unknown";
try {
const personalAccessToken = await createPersonalAccessTokenFromAuthorizationCode(
parsedParams.data.authorizationCode,
userId
);
return typedjson({
success: true as const,
source,
clientName,
});
} catch (error) {
if (error instanceof Response) {
throw error;
}
if (error instanceof Error) {
return typedjson({
success: false as const,
error: error.message,
source,
clientName,
});
}
logger.error(JSON.stringify(error));
throw new Response(undefined, {
status: 400,
statusText: "Something went wrong, if this problem persists please contact support.",
});
}
};
export default function Page() {
const result = useTypedLoaderData<typeof loader>();
return (
<AppContainer>
<MainCenteredContainer className="max-w-[22rem]">
<div className="flex flex-col items-center space-y-4">
{result.success ? (
<div>
<Header1 className="mb-2 flex items-center gap-1">
<Icon icon={CheckCircleIcon} className="h-6 w-6 text-emerald-500" /> Successfully
authenticated
</Header1>
<Paragraph>{getInstructionsForSource(result.source, result.clientName)}</Paragraph>
</div>
) : (
<div>
<Header1 className="mb-2">Authentication failed</Header1>
<Callout variant="error" className="my-2">
{result.error}
</Callout>
<Paragraph spacing>
There was a problem authenticating you, please try logging in with your CLI again.
</Paragraph>
</div>
)}
</div>
</MainCenteredContainer>
</AppContainer>
);
}
const prettyClientNames: Record<string, string> = {
"claude-code": "Claude Code",
"cursor-vscode": "Cursor",
"Visual Studio Code": "VSCode",
"windsurf-client": "Windsurf",
"claude-ai": "Claude Desktop",
};
function getInstructionsForSource(source: string, clientName: string) {
if (source === "mcp") {
if (clientName) {
return `Return to your ${prettyClientNames[clientName] ?? clientName} to continue.`;
}
}
return `Return to your terminal to continue.`;
}