Skip to content

Commit fc74c8e

Browse files
committed
feat: API keys
1 parent 1592b8d commit fc74c8e

File tree

12 files changed

+1959
-5
lines changed

12 files changed

+1959
-5
lines changed

apps/dashboard/app/(main)/settings/_components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export { ApiKeyCreateDialog } from '@/components/organizations/api-key-create-dialog';
2+
export { ApiKeyList } from '@/components/organizations/api-key-list';
13
export { AccountDeletion } from './account-deletion';
24
export { EmailForm } from './email-form';
35
export { PasswordForm } from './password-form';

apps/dashboard/app/(main)/settings/_components/settings-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function SettingsSidebar({
2525
className,
2626
}: SettingsSidebarProps) {
2727
return (
28-
<nav className={cn('flex flex-col space-y-1', className)}>
28+
<nav className={cn('flex flex-col gap-1', className)}>
2929
{items.map((item) => (
3030
<Button
3131
className="w-full justify-start"

apps/dashboard/app/(main)/settings/page.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
} from '@phosphor-icons/react';
99
import dynamic from 'next/dynamic';
1010
import { useQueryState } from 'nuqs';
11+
import { useState } from 'react';
12+
import { ApiKeyDetailDialog } from '@/components/organizations/api-key-detail-dialog';
1113
import { Button } from '@/components/ui/button';
1214
import {
1315
Card,
@@ -17,7 +19,7 @@ import {
1719
CardTitle,
1820
} from '@/components/ui/card';
1921
import { Skeleton } from '@/components/ui/skeleton';
20-
import { cn } from '@/lib/utils';
22+
import { ApiKeyCreateDialog, ApiKeyList } from './_components';
2123
import { type NavItem, SettingsSidebar } from './_components/settings-sidebar';
2224

2325
const EmailForm = dynamic(
@@ -97,7 +99,12 @@ const TimezonePreferences = dynamic(
9799
}
98100
);
99101

100-
type SettingsTab = 'profile' | 'account' | 'security' | 'notifications';
102+
type SettingsTab =
103+
| 'profile'
104+
| 'account'
105+
| 'security'
106+
| 'api-keys'
107+
| 'notifications';
101108

102109
const tabs: NavItem[] = [
103110
{
@@ -115,6 +122,11 @@ const tabs: NavItem[] = [
115122
label: 'Security',
116123
icon: ShieldIcon,
117124
},
125+
{
126+
id: 'api-keys',
127+
label: 'API keys',
128+
icon: GearSixIcon,
129+
},
118130
{
119131
id: 'notifications',
120132
label: 'Notifications',
@@ -290,6 +302,7 @@ export default function SettingsPage() {
290302
</CardContent>
291303
</Card>
292304
)}
305+
{activeTab === 'api-keys' && <ApiKeysSection />}
293306
{activeTab === 'notifications' && (
294307
<div className="flex h-full items-center justify-center">
295308
<div className="text-center">
@@ -308,3 +321,42 @@ export default function SettingsPage() {
308321
</div>
309322
);
310323
}
324+
325+
function ApiKeysSection() {
326+
const [open, setOpen] = useState(false);
327+
const [createdSecret, setCreatedSecret] = useState<null | {
328+
id: string;
329+
secret: string;
330+
prefix: string;
331+
start: string;
332+
}>(null);
333+
const [selectedId, setSelectedId] = useState<string | null>(null);
334+
return (
335+
<div className="space-y-4">
336+
<ApiKeyList
337+
onCreateNew={() => setOpen(true)}
338+
onSelect={(id) => setSelectedId(id)}
339+
/>
340+
{createdSecret && (
341+
<div className="rounded border p-3 text-sm">
342+
<div className="mb-1 font-medium">Copy your secret now</div>
343+
<code className="block break-all">{createdSecret.secret}</code>
344+
</div>
345+
)}
346+
<ApiKeyCreateDialog
347+
onCreated={(res) => setCreatedSecret(res)}
348+
onOpenChange={setOpen}
349+
open={open}
350+
/>
351+
<ApiKeyDetailDialog
352+
keyId={selectedId}
353+
onOpenChange={(o) => {
354+
if (!o) {
355+
setSelectedId(null);
356+
}
357+
}}
358+
open={!!selectedId}
359+
/>
360+
</div>
361+
);
362+
}

0 commit comments

Comments
 (0)