Skip to content

Commit 27236fc

Browse files
committed
fix: sep out keys and product secret in settings
1 parent a8886b0 commit 27236fc

4 files changed

Lines changed: 191 additions & 3 deletions

File tree

apps/webapp/src/app/(authenticated)/settings/layout.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ const SidebarItems = [
4949
href: "/settings/organization/members",
5050
icon: IconUsers,
5151
},
52+
{
53+
label: "API",
54+
href: "/settings/organization/api-keys",
55+
icon: IconKey,
56+
},
5257
],
5358
},
5459
{
@@ -59,7 +64,11 @@ const SidebarItems = [
5964
href: "/settings/product/general",
6065
icon: IconBuilding,
6166
},
62-
{ label: "API & Secrets", href: "/settings/product/api", icon: IconKey },
67+
{
68+
label: "Secrets",
69+
href: "/settings/product/secrets",
70+
icon: IconKey,
71+
},
6372
],
6473
},
6574
];
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client";
2+
3+
import { Separator } from "@refref/ui/components/separator";
4+
import { ProductSecretsCard } from "@/components/settings/product/product-secrets-card";
5+
6+
export default function ProductSecretsSettings() {
7+
return (
8+
<div className="flex flex-col gap-6 p-6 w-full max-w-[var(--content-max-width)] mx-auto">
9+
<div>
10+
<h1 className="text-2xl font-semibold tracking-tight">Secrets</h1>
11+
<p className="text-sm text-muted-foreground mt-1">
12+
View your product credentials for API integration
13+
</p>
14+
</div>
15+
16+
<Separator />
17+
18+
<div className="flex flex-col gap-6">
19+
<ProductSecretsCard />
20+
</div>
21+
</div>
22+
);
23+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Copy, Eye, EyeOff } from "lucide-react";
5+
import { toast } from "sonner";
6+
import {
7+
Card,
8+
CardContent,
9+
CardFooter,
10+
} from "@refref/ui/components/card";
11+
import { Button } from "@refref/ui/components/button";
12+
import { Input } from "@refref/ui/components/input";
13+
import { Label } from "@refref/ui/components/label";
14+
import { Skeleton } from "@refref/ui/components/skeleton";
15+
import { Separator } from "@refref/ui/components/separator";
16+
import { api } from "@/trpc/react";
17+
18+
export function ProductSecretsCard() {
19+
const [showSecret, setShowSecret] = useState(false);
20+
const [copiedField, setCopiedField] = useState<string | null>(null);
21+
22+
const { data: product, isLoading: productLoading } =
23+
api.product.getCurrent.useQuery();
24+
25+
const { data: secrets, isLoading: secretsLoading } =
26+
api.productSecrets.get.useQuery(product?.id ?? "", {
27+
enabled: !!product?.id,
28+
});
29+
30+
const isLoading = productLoading || secretsLoading;
31+
32+
const copyToClipboard = async (value: string, fieldName: string) => {
33+
try {
34+
await navigator.clipboard.writeText(value);
35+
setCopiedField(fieldName);
36+
toast.success(`${fieldName} copied to clipboard`);
37+
setTimeout(() => setCopiedField(null), 2000);
38+
} catch {
39+
toast.error(`Failed to copy ${fieldName}`);
40+
}
41+
};
42+
43+
if (isLoading || !secrets) {
44+
return (
45+
<Card className="w-full pb-0 text-start">
46+
<div className="flex justify-between items-center px-6 pt-6">
47+
<div className="space-y-2">
48+
<div className="text-base font-medium">Product Credentials</div>
49+
<div className="text-sm text-muted-foreground">
50+
Use these credentials to authenticate API requests
51+
</div>
52+
</div>
53+
</div>
54+
<CardContent className="space-y-4">
55+
<div className="space-y-2">
56+
<Skeleton className="h-4 w-20" />
57+
<Skeleton className="h-9 w-full" />
58+
</div>
59+
<div className="space-y-2">
60+
<Skeleton className="h-4 w-24" />
61+
<Skeleton className="h-9 w-full" />
62+
</div>
63+
</CardContent>
64+
<Separator />
65+
<CardFooter className="flex flex-col justify-between gap-4 rounded-b-xl md:flex-row border-t bg-sidebar !py-5">
66+
<div className="text-center text-muted-foreground text-xs md:text-start md:text-sm">
67+
Keep your client secret secure and never share it publicly.
68+
</div>
69+
</CardFooter>
70+
</Card>
71+
);
72+
}
73+
74+
const credentials = [
75+
{
76+
id: "clientId",
77+
label: "Client ID",
78+
value: secrets.clientId,
79+
masked: false,
80+
},
81+
{
82+
id: "clientSecret",
83+
label: "Client Secret",
84+
value: secrets.clientSecret,
85+
masked: true,
86+
},
87+
];
88+
89+
return (
90+
<Card className="w-full pb-0 text-start">
91+
<div className="flex justify-between items-center px-6 pt-6">
92+
<div className="space-y-2">
93+
<div className="text-base font-medium">Product Credentials</div>
94+
<div className="text-sm text-muted-foreground">
95+
Use these credentials to authenticate API requests
96+
</div>
97+
</div>
98+
</div>
99+
<CardContent className="space-y-4">
100+
{credentials.map((credential) => (
101+
<div key={credential.id} className="space-y-2">
102+
<Label htmlFor={credential.id}>{credential.label}</Label>
103+
<div className="flex items-center gap-2">
104+
<div className="relative flex-1">
105+
<Input
106+
id={credential.id}
107+
value={credential.value}
108+
type={credential.masked && !showSecret ? "password" : "text"}
109+
readOnly
110+
className="font-mono pr-10"
111+
/>
112+
{credential.masked && (
113+
<Button
114+
type="button"
115+
variant="ghost"
116+
size="sm"
117+
className="absolute right-0 top-0 h-full px-3 hover:bg-transparent"
118+
onClick={() => setShowSecret(!showSecret)}
119+
>
120+
{showSecret ? (
121+
<EyeOff className="h-4 w-4 text-muted-foreground" />
122+
) : (
123+
<Eye className="h-4 w-4 text-muted-foreground" />
124+
)}
125+
</Button>
126+
)}
127+
</div>
128+
<Button
129+
variant="outline"
130+
size="sm"
131+
onClick={() =>
132+
copyToClipboard(credential.value, credential.label)
133+
}
134+
className="shrink-0"
135+
>
136+
<Copy className="h-4 w-4 mr-1" />
137+
{copiedField === credential.label ? "Copied!" : "Copy"}
138+
</Button>
139+
</div>
140+
</div>
141+
))}
142+
</CardContent>
143+
<Separator />
144+
<CardFooter className="flex flex-col justify-between gap-4 rounded-b-xl md:flex-row border-t bg-sidebar !py-5">
145+
<div className="text-center text-muted-foreground text-xs md:text-start md:text-sm">
146+
Keep your client secret secure and never share it publicly.
147+
</div>
148+
</CardFooter>
149+
</Card>
150+
);
151+
}

apps/webapp/src/server/api/routers/search.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ const settingsPages = [
3131
name: "Product",
3232
href: `/settings/product/general`,
3333
},
34+
{
35+
id: "product-secrets-settings",
36+
name: "Product Secrets",
37+
href: "/settings/product/secrets",
38+
},
3439
{
3540
id: "api-settings",
36-
name: "API & Secrets",
37-
href: "/settings/product/api",
41+
name: "API",
42+
href: "/settings/organization/api-keys",
3843
},
3944
];
4045

0 commit comments

Comments
 (0)