Skip to content

Commit c4e26fb

Browse files
authored
Merge pull request #240 from n4ze3m/next
v1.7.8
2 parents b6c0423 + 3fe00b3 commit c4e26fb

33 files changed

+4756
-2009
lines changed

.lintstagedrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"app/ui/*.tsx" : "eslint --fix"
3+
}

app/ui/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "app",
33
"private": true,
4-
"version": "1.7.7",
4+
"version": "1.7.8",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2+
import api from "../../../../services/api";
3+
import { Form, Input, Modal, Table, Tooltip, notification } from "antd";
4+
import { TrashIcon } from "@heroicons/react/24/outline";
5+
import axios from "axios";
6+
import React from "react";
7+
import { CopyBtn } from "../../../Common/CopyBtn";
8+
9+
export const UserApiKey = () => {
10+
const queryClient = useQueryClient();
11+
const [openModal, setOpenModal] = React.useState(false);
12+
const [apiKey, setApiKey] = React.useState("");
13+
const [openApiKey, setOpenApiKey] = React.useState(false);
14+
15+
const { data, isLoading } = useQuery({
16+
queryKey: ["fetchUserApiKey"],
17+
queryFn: async () => {
18+
const res = await api.get("/user/api-key");
19+
const data = res.data["data"] as {
20+
id: number;
21+
api_key: string;
22+
name: string;
23+
createdAt: string;
24+
}[];
25+
return data;
26+
},
27+
});
28+
29+
const onDeleteApiKey = async (id: number) => {
30+
await api.delete(`/user/api-key/${id}`);
31+
};
32+
33+
const { mutate: deleteApiKey, isLoading: isDeleting } = useMutation(
34+
onDeleteApiKey,
35+
{
36+
onSuccess: () => {
37+
queryClient.invalidateQueries({
38+
queryKey: ["fetchUserApiKey"],
39+
});
40+
notification.success({
41+
message: "Success",
42+
description: "API Key deleted",
43+
});
44+
},
45+
onError: (error) => {
46+
if (axios.isAxiosError(error)) {
47+
const message = error.response?.data?.message;
48+
notification.error({
49+
message: "Error",
50+
description: message,
51+
});
52+
return;
53+
}
54+
55+
notification.error({
56+
message: "Error",
57+
description: "Something went wrong",
58+
});
59+
},
60+
}
61+
);
62+
63+
const onCreateApiKey = async (values: any) => {
64+
const response = await api.post("/user/api-key", values);
65+
66+
return response.data as {
67+
data: {
68+
api_key: string;
69+
};
70+
};
71+
};
72+
73+
const { mutate: createApiKey, isLoading: isCreating } = useMutation(
74+
onCreateApiKey,
75+
{
76+
onSuccess: (data) => {
77+
queryClient.invalidateQueries({
78+
queryKey: ["fetchUserApiKey"],
79+
});
80+
setApiKey(data.data.api_key);
81+
setOpenApiKey(true);
82+
setOpenModal(false);
83+
},
84+
onError: (error) => {
85+
if (axios.isAxiosError(error)) {
86+
const message = error.response?.data?.message;
87+
notification.error({
88+
message: "Error",
89+
description: message,
90+
});
91+
return;
92+
}
93+
94+
notification.error({
95+
message: "Error",
96+
description: "Something went wrong",
97+
});
98+
},
99+
}
100+
);
101+
102+
return (
103+
<div className="space-y-4">
104+
<div>
105+
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
106+
API Key
107+
</h2>
108+
109+
<p className="mt-1 text-sm leading-6 text-gray-500 dark:text-gray-200">
110+
Manage your Dialoqbase API key
111+
</p>
112+
</div>
113+
114+
<div className="flex justify-end">
115+
<button
116+
onClick={() => setOpenModal(true)}
117+
className="inline-flex items-center px-3 py-1 border border-transparent text-sm font-medium rounded shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
118+
Generate New API Key
119+
</button>
120+
</div>
121+
<div>
122+
<Table
123+
columns={[
124+
{
125+
title: "Name",
126+
dataIndex: "name",
127+
key: "name",
128+
},
129+
{
130+
title: "API Key",
131+
dataIndex: "api_key",
132+
key: "api_key",
133+
},
134+
{
135+
title: "Created At",
136+
dataIndex: "createdAt",
137+
key: "createdAt",
138+
render: (text) => new Date(text).toLocaleString(),
139+
},
140+
{
141+
title: "Action",
142+
key: "action",
143+
render: (_, record) => (
144+
<Tooltip title="Delete API Key">
145+
<button
146+
type="button"
147+
disabled={isDeleting}
148+
onClick={() => {
149+
const confirm = window.confirm(
150+
"Are you sure you want to delete this API Key?"
151+
);
152+
153+
if (confirm) {
154+
deleteApiKey(record.id);
155+
}
156+
}}
157+
className="text-red-400 hover:text-red-500"
158+
>
159+
<TrashIcon className="h-5 w-5" />
160+
</button>
161+
</Tooltip>
162+
),
163+
},
164+
]}
165+
loading={isLoading}
166+
dataSource={data || []}
167+
/>
168+
</div>
169+
170+
<Modal
171+
title="Generate New API Key"
172+
open={openModal}
173+
onCancel={() => setOpenModal(false)}
174+
footer={null}
175+
>
176+
<Form
177+
layout="vertical"
178+
onFinish={(values) => {
179+
createApiKey(values);
180+
}}
181+
>
182+
<Form.Item
183+
label="Name"
184+
name="name"
185+
rules={[
186+
{
187+
required: true,
188+
message: "Please input your name!",
189+
},
190+
]}
191+
>
192+
<Input size="large" type="text" placeholder="Name" />
193+
</Form.Item>
194+
<div className="text-right">
195+
<button
196+
type="submit"
197+
className="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
198+
>
199+
{isCreating ? "Generating..." : "Generate"}
200+
</button>
201+
</div>
202+
</Form>
203+
</Modal>
204+
205+
<Modal
206+
title="API Key Generated Successfully"
207+
open={openApiKey}
208+
onCancel={() => setOpenApiKey(false)}
209+
footer={null}
210+
>
211+
<p className="text-xs text-gray-500 dark:text-gray-200">
212+
Your API Key has been generated successfully. Please copy the API Key
213+
and save it in a safe place. You will not be able to see this API Key
214+
again.
215+
</p>
216+
217+
<div className="flex mt-5">
218+
<Input
219+
size="large"
220+
type="text"
221+
value={apiKey}
222+
readOnly
223+
className="w-full"
224+
/>
225+
226+
<CopyBtn value={apiKey} />
227+
</div>
228+
</Modal>
229+
</div>
230+
);
231+
};

app/ui/src/routes/settings/root.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import axios from "axios";
77
import { SettingsLayout } from "../../Layout/SettingsLayout";
88
import { SkeletonLoading } from "../../components/Common/SkeletonLoading";
99
import { useNavigate } from "react-router-dom";
10+
import { UserApiKey } from "../../components/Settings/Profile/ApiKey";
1011

1112
export default function SettingsRoot() {
1213
const [form] = Form.useForm();
@@ -208,6 +209,8 @@ export default function SettingsRoot() {
208209
</div>
209210
</dl>
210211
</div>
212+
213+
<UserApiKey />
211214
</>
212215
)}
213216
{status === "loading" && <SkeletonLoading />}

0 commit comments

Comments
 (0)