mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
Replace env key textareas with a masked EnvFileViewer editor component.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7055d5699b
commit
87b6eee8f7
@ -1,5 +1,116 @@
|
||||
"use client";
|
||||
|
||||
import { getPublicEnvVar } from '@/lib/env';
|
||||
import { Button, CopyField, Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui";
|
||||
import React, { useState } from "react";
|
||||
import { EyeIcon, EyeSlashIcon, CopyIcon, CheckIcon, FileTextIcon } from "@phosphor-icons/react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
||||
|
||||
type EnvFileViewerProps = {
|
||||
filename: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function EnvFileViewer({ filename, value }: EnvFileViewerProps) {
|
||||
const [revealAll, setRevealAll] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const lines = value.split("\n").map((line, idx) => {
|
||||
const eqIndex = line.indexOf("=");
|
||||
if (eqIndex === -1) return { key: `comment_${idx}`, val: line, isComment: true };
|
||||
const key = line.substring(0, eqIndex);
|
||||
const val = line.substring(eqIndex + 1);
|
||||
return { key, val, isComment: false };
|
||||
});
|
||||
|
||||
const handleCopyAll = () => {
|
||||
runAsynchronously(async () => {
|
||||
await navigator.clipboard.writeText(value);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden transition-all duration-150 hover:transition-none rounded-2xl bg-white/90 dark:bg-background/60 dark:backdrop-blur-xl ring-1 ring-black/[0.06] hover:ring-black/[0.1] dark:ring-white/[0.06] dark:hover:ring-white/[0.1] border border-black/[0.06] dark:border-white/[0.06] shadow-none w-full flex flex-col">
|
||||
{/* Tab/Header Bar */}
|
||||
<div className="text-muted-foreground font-medium pl-4 pr-3 text-sm flex justify-between items-center py-2.5 bg-black/[0.015] dark:bg-white/[0.015] border-b border-black/[0.06] dark:border-white/[0.06]">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileTextIcon className="w-4 h-4 text-muted-foreground/80" />
|
||||
<span className="font-mono text-xs font-semibold text-foreground/80">{filename}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRevealAll(!revealAll)}
|
||||
className="h-8 w-8 flex items-center justify-center rounded-lg text-muted-foreground/75 hover:text-foreground hover:bg-black/[0.04] dark:hover:bg-white/[0.04] transition-all"
|
||||
title={revealAll ? "Mask values" : "Reveal values"}
|
||||
aria-label={revealAll ? "Mask values" : "Reveal values"}
|
||||
>
|
||||
{revealAll ? (
|
||||
<EyeSlashIcon className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<EyeIcon className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopyAll}
|
||||
title={copied ? "Copied" : "Copy file"}
|
||||
aria-label={copied ? "Copied" : "Copy file"}
|
||||
className={cn(
|
||||
"h-8 w-8 flex items-center justify-center rounded-lg border transition-all",
|
||||
copied
|
||||
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"
|
||||
: "bg-white/50 dark:bg-background/40 border-black/[0.08] dark:border-white/[0.06] text-muted-foreground hover:text-foreground hover:bg-white dark:hover:bg-background/80"
|
||||
)}
|
||||
>
|
||||
{copied ? (
|
||||
<CheckIcon className="h-3.5 w-3.5 text-emerald-500 dark:text-emerald-400" weight="bold" />
|
||||
) : (
|
||||
<CopyIcon className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Editor Body */}
|
||||
<div className="overflow-x-auto px-3 py-2.5 font-mono text-xs bg-black/[0.01] dark:bg-white/[0.005] select-text">
|
||||
<table className="w-full border-collapse">
|
||||
<tbody>
|
||||
{lines.map((line, idx) => {
|
||||
return (
|
||||
<tr key={idx} className="group hover:bg-black/[0.02] dark:hover:bg-white/[0.02] transition-colors leading-relaxed">
|
||||
<td className="py-0.5 w-full">
|
||||
<div className="flex items-center justify-between gap-4 w-full">
|
||||
<div className="flex items-center flex-wrap whitespace-pre">
|
||||
{line.isComment ? (
|
||||
<span className="text-muted-foreground/50 italic">{line.val}</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-indigo-600 dark:text-indigo-400 font-medium select-all">{line.key}</span>
|
||||
<span className="text-muted-foreground/50 mx-1">=</span>
|
||||
{revealAll ? (
|
||||
<span className="text-teal-600 dark:text-teal-400 font-medium break-all select-all">{line.val}</span>
|
||||
) : (
|
||||
<span className="text-muted-foreground/45 tracking-[0.25em] font-sans text-xs select-none">••••••••••••••••••••</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getEnvFileContent(props: {
|
||||
projectId: string,
|
||||
@ -90,6 +201,7 @@ export function APIEnvKeys(props: {
|
||||
<CopyField
|
||||
type="input"
|
||||
monospace
|
||||
isSecret
|
||||
value={props.secretServerKey}
|
||||
label="Secret Server Key"
|
||||
helper="This key is used on the server-side and can be used to perform actions on behalf of your users. Keep it safe."
|
||||
@ -99,6 +211,7 @@ export function APIEnvKeys(props: {
|
||||
<CopyField
|
||||
type="input"
|
||||
monospace
|
||||
isSecret
|
||||
value={props.superSecretAdminKey}
|
||||
label="Super Secret Admin Key"
|
||||
helper="This key is for administrative use only. Anyone owning this key will be able to create unlimited new keys and revoke any other keys. Be careful!"
|
||||
@ -117,13 +230,7 @@ export function NextJsEnvKeys(props: {
|
||||
const envFileContent = getEnvFileContent(props);
|
||||
|
||||
return (
|
||||
<CopyField
|
||||
type="textarea"
|
||||
monospace
|
||||
height={envFileContent.split("\n").length * 26}
|
||||
value={envFileContent}
|
||||
fixedSize
|
||||
/>
|
||||
<EnvFileViewer filename=".env.local" value={envFileContent} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -141,12 +248,6 @@ export function ViteEnvKeys(props: {
|
||||
.join("\n");
|
||||
|
||||
return (
|
||||
<CopyField
|
||||
type="textarea"
|
||||
monospace
|
||||
height={envFileContent.split("\n").length * 26}
|
||||
value={envFileContent}
|
||||
fixedSize
|
||||
/>
|
||||
<EnvFileViewer filename=".env" value={envFileContent} />
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user