aboutsummaryrefslogtreecommitdiff
path: root/packages/web/src/app/dashboard/settings/api-key-settings.tsx
blob: 53158ab9e3c0551c8877d7e28b446fca504bab24 (plain) (blame)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
"use client";

import { useState } from "react";
import { api } from "~/trpc/react";

function CreateApiKeyForm() {
	const [name, setName] = useState("");
	const [newKey, setNewKey] = useState<string | null>(null);
	const [copied, setCopied] = useState(false);
	const trpcUtilities = api.useUtils();
	const createKey = api.apiKey.create.useMutation({
		onSuccess: async (data) => {
			setNewKey(data.apiKey);
			setName("");
			await trpcUtilities.apiKey.invalidate();
		},
	});
	const handleCopy = async () => {
		if (newKey) {
			await navigator.clipboard.writeText(newKey);
			setCopied(true);
			setTimeout(() => setCopied(false), 2000);
		}
	};

	if (newKey) {
		return (
			<div className="border border-[#2a2a2a] bg-[#0f0f0f] p-4">
				<p className="mb-2 text-sm text-[#666666]">
					your new api key (copy it now - you will not be able to see it again):
				</p>
				<div className="flex gap-2">
					<code className="flex-1 overflow-x-auto border border-[#2a2a2a] bg-[#070707] p-2 text-sm text-white">
						{newKey}
					</code>
					<button
						className="border border-[#2a2a2a] bg-[#0f0f0f] px-4 py-2 text-white transition hover:border-[#666666]"
						onClick={handleCopy}
						type="button"
					>
						{copied ? "copied!" : "copy"}
					</button>
				</div>
				<button
					className="mt-3 border border-[#2a2a2a] bg-[#0f0f0f] px-4 py-2 text-[#999999] transition hover:border-[#666666] hover:text-white"
					onClick={() => setNewKey(null)}
					type="button"
				>
					done
				</button>
			</div>
		);
	}

	return (
		<form
			className="flex w-full gap-2"
			onSubmit={(formEvent) => {
				formEvent.preventDefault();

				if (name.trim()) {
					createKey.mutate({ name });
				}
			}}
		>
			<input
				className="flex-1 border border-[#2a2a2a] bg-[#0f0f0f] px-3 py-2 text-white placeholder:text-[#666666] focus:border-[#666666] focus:outline-none"
				onChange={(inputEvent) => setName(inputEvent.target.value)}
				placeholder="key name (e.g., development, claude-code)"
				value={name}
			/>
			<button
				className="border border-[#2a2a2a] bg-[#0f0f0f] px-4 py-2 text-white transition hover:border-[#666666] disabled:text-[#666666]"
				disabled={createKey.isPending || !name.trim()}
				type="submit"
			>
				{createKey.isPending ? "creating ..." : "create key"}
			</button>
		</form>
	);
}

function ApiKeyList() {
	const [keys] = api.apiKey.list.useSuspenseQuery();
	const trpcUtilities = api.useUtils();
	const revokeKey = api.apiKey.revoke.useMutation({
		onSuccess: async () => {
			await trpcUtilities.apiKey.invalidate();
		},
	});

	if (keys.length === 0) {
		return (
			<div className="border border-[#2a2a2a] bg-[#0f0f0f] p-4 text-center">
				<p className="text-[#666666]">
					no api keys yet. create one to use with the mcp server.
				</p>
			</div>
		);
	}

	return (
		<div className="flex w-full flex-col gap-2">
			{keys.map((key) => (
				<div
					className="flex items-center justify-between gap-4 border border-[#2a2a2a] bg-[#0f0f0f] p-3"
					key={key.id}
				>
					<div className="flex-1">
						<p className="text-white">{key.name}</p>
						<p className="mt-1 text-xs text-[#666666]">
							<code className="text-[#999999]">imemio_{key.keyPrefix} ...</code> |{" "}
							{key.lastUsedAt
								? `last used ${key.lastUsedAt.toLocaleDateString()}`
								: "never used"}
						</p>
					</div>
					<button
						className="border border-[#2a2a2a] bg-[#0f0f0f] px-3 py-1 text-sm text-[#999999] transition hover:border-[#666666] hover:text-white"
						disabled={revokeKey.isPending}
						onClick={() => revokeKey.mutate({ id: key.id })}
						type="button"
					>
						{revokeKey.isPending ? "revoking ..." : "revoke"}
					</button>
				</div>
			))}
		</div>
	);
}

export function ApiKeySettings() {
	return (
		<div className="flex w-full flex-col gap-4">
			<CreateApiKeyForm />
			<ApiKeyList />
		</div>
	);
}