feat(Jwt): 重构 JWT 组件,新增常量和结果视图
- 新增 constants.ts 文件,定义 JWT 输入占位符和配置常量,提升代码可读性。 - 删除 JwtSection 组件,重构为 JwtResultView 组件,简化结果展示逻辑。 - 更新 index.tsx,使用 JwtResultView 组件替代原有的 JWT 结果展示方式,优化页面结构。
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
import { CopyButton } from '@/components/CopyButton';
|
|
||||||
import { stringifyJson } from '@/utils/jwt';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
interface JwtSectionProps {
|
|
||||||
title: string;
|
|
||||||
content: unknown;
|
|
||||||
colorClass: string;
|
|
||||||
bgClass: string;
|
|
||||||
borderClass: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function JwtSection({
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
colorClass,
|
|
||||||
bgClass,
|
|
||||||
borderClass,
|
|
||||||
}: JwtSectionProps) {
|
|
||||||
return (
|
|
||||||
<div className={cn('p-4 rounded-xl border border-solid', bgClass, borderClass)}>
|
|
||||||
<div className="flex justify-between items-center mb-2 select-none">
|
|
||||||
<span className={cn('text-xs font-bold tracking-wider uppercase', colorClass)}>
|
|
||||||
{title}
|
|
||||||
</span>
|
|
||||||
<CopyButton
|
|
||||||
text={JSON.stringify(content)}
|
|
||||||
className="h-6 w-6 rounded-md border text-muted-foreground"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<pre className="m-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text">
|
|
||||||
{content ? stringifyJson(content) : '无法解析'}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { CopyButton } from '@/components/CopyButton';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
interface JwtCopyBlockProps {
|
||||||
|
title: string;
|
||||||
|
copyText: string;
|
||||||
|
titleClassName?: string;
|
||||||
|
containerClassName?: string;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JwtCopyBlock({
|
||||||
|
title,
|
||||||
|
copyText,
|
||||||
|
titleClassName,
|
||||||
|
containerClassName,
|
||||||
|
children,
|
||||||
|
}: JwtCopyBlockProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn('p-4 rounded-xl border shadow-sm', containerClassName)}>
|
||||||
|
<div className="flex justify-between items-center mb-2 select-none">
|
||||||
|
<span className={cn('text-xs font-bold tracking-wider uppercase', titleClassName)}>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
<CopyButton text={copyText} className="h-6 w-6 rounded-md border text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { stringifyJson } from '@/utils/jwt';
|
||||||
|
import type { JwtResult } from '@/utils/jwt';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import {
|
||||||
|
JWT_JSON_CONTENT_CLASS,
|
||||||
|
JWT_SECTION_CONFIG,
|
||||||
|
JWT_SIGNATURE_SECTION,
|
||||||
|
JWT_TEXT_CONTENT_CLASS,
|
||||||
|
} from '../constants';
|
||||||
|
import JwtCopyBlock from './JwtCopyBlock';
|
||||||
|
|
||||||
|
interface JwtResultViewProps {
|
||||||
|
result: JwtResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function JwtResultView({ result }: JwtResultViewProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{JWT_SECTION_CONFIG.map(({ contentKey, title, colorClass, bgClass, borderClass }) => {
|
||||||
|
const content = result[contentKey];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JwtCopyBlock
|
||||||
|
key={contentKey}
|
||||||
|
title={title}
|
||||||
|
copyText={JSON.stringify(content)}
|
||||||
|
titleClassName={colorClass}
|
||||||
|
containerClassName={cn('border-solid', bgClass, borderClass)}
|
||||||
|
>
|
||||||
|
<pre className={JWT_JSON_CONTENT_CLASS}>
|
||||||
|
{content ? stringifyJson(content) : '无法解析'}
|
||||||
|
</pre>
|
||||||
|
</JwtCopyBlock>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<JwtCopyBlock
|
||||||
|
title={JWT_SIGNATURE_SECTION.title}
|
||||||
|
copyText={result.signature || ''}
|
||||||
|
titleClassName={JWT_SIGNATURE_SECTION.titleClassName}
|
||||||
|
containerClassName={JWT_SIGNATURE_SECTION.containerClassName}
|
||||||
|
>
|
||||||
|
<span className={JWT_TEXT_CONTENT_CLASS}>
|
||||||
|
{result.signature || JWT_SIGNATURE_SECTION.emptyLabel}
|
||||||
|
</span>
|
||||||
|
</JwtCopyBlock>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export const JWT_INPUT_PLACEHOLDER = '在此粘贴 JWT 令牌 (Encoded JWT)...';
|
||||||
|
|
||||||
|
export type JwtSectionContentKey = 'header' | 'payload';
|
||||||
|
|
||||||
|
export interface JwtSectionConfig {
|
||||||
|
contentKey: JwtSectionContentKey;
|
||||||
|
title: string;
|
||||||
|
colorClass: string;
|
||||||
|
borderClass: string;
|
||||||
|
bgClass: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const JWT_SECTION_CONFIG: JwtSectionConfig[] = [
|
||||||
|
{
|
||||||
|
contentKey: 'header',
|
||||||
|
title: 'HEADER: 算法 & 令牌类型',
|
||||||
|
colorClass: 'text-[#fb015b] dark:text-rose-400',
|
||||||
|
borderClass: 'border-[#fb015b]/20 dark:border-rose-500/20',
|
||||||
|
bgClass: 'bg-[#fb015b]/5 dark:bg-rose-500/5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentKey: 'payload',
|
||||||
|
title: 'PAYLOAD: 数据',
|
||||||
|
colorClass: 'text-[#a03aff] dark:text-purple-400',
|
||||||
|
borderClass: 'border-[#a03aff]/20 dark:border-purple-500/20',
|
||||||
|
bgClass: 'bg-[#a03aff]/5 dark:bg-purple-500/5',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const JWT_SIGNATURE_SECTION = {
|
||||||
|
title: '签名',
|
||||||
|
titleClassName: 'text-muted-foreground/90',
|
||||||
|
containerClassName: 'border border-border bg-secondary/40',
|
||||||
|
emptyLabel: '无签名',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const JWT_JSON_CONTENT_CLASS =
|
||||||
|
'm-0 p-3 bg-muted/30 dark:bg-muted/10 rounded-lg text-xs font-mono overflow-x-auto whitespace-pre-wrap break-all border border-border/50 text-foreground/90 leading-relaxed select-text';
|
||||||
|
|
||||||
|
export const JWT_TEXT_CONTENT_CLASS =
|
||||||
|
'block text-xs font-mono break-all text-foreground/80 bg-muted/30 dark:bg-muted/10 p-3 rounded-lg border border-border/50 leading-relaxed select-text';
|
||||||
+15
-55
@@ -1,66 +1,26 @@
|
|||||||
import TextInputArea from '@/components/TextInputArea';
|
import TextInputArea from '@/components/TextInputArea';
|
||||||
import { CopyButton } from '@/components/CopyButton';
|
import JwtResultView from './components/JwtResultView';
|
||||||
import EmptyPlaceholder from '@/components/EmptyPlaceholder';
|
import { JWT_INPUT_PLACEHOLDER } from './constants';
|
||||||
import JwtSection from './JwtSection';
|
|
||||||
import { useJwt } from './useJwt';
|
import { useJwt } from './useJwt';
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const { jwtInput, result, handleChange, handleClear } = useJwt();
|
const { jwtInput, result, handleChange, handleClear } = useJwt();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
<div className="p-4 w-full flex flex-col gap-4 select-none">
|
||||||
<div className="flex flex-col gap-4">
|
<TextInputArea
|
||||||
<TextInputArea
|
minRows={5}
|
||||||
minRows={5}
|
maxRows={10}
|
||||||
maxRows={10}
|
placeholder={JWT_INPUT_PLACEHOLDER}
|
||||||
placeholder={'在此粘贴 JWT 令牌 (Encoded JWT)...'}
|
value={jwtInput}
|
||||||
value={jwtInput}
|
onChange={handleChange}
|
||||||
onChange={handleChange}
|
allowCopy={true}
|
||||||
allowCopy={true}
|
showClear={true}
|
||||||
showClear={true}
|
externalError={result?.error || undefined}
|
||||||
externalError={result?.error || undefined}
|
onClear={handleClear}
|
||||||
onClear={handleClear}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
{result && !result.error && (
|
{result && !result.error && <JwtResultView result={result} />}
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<JwtSection
|
|
||||||
title={'HEADER: 算法 & 令牌类型'}
|
|
||||||
content={result.header}
|
|
||||||
colorClass="text-[#fb015b] dark:text-rose-400"
|
|
||||||
borderClass="border-[#fb015b]/20 dark:border-rose-500/20"
|
|
||||||
bgClass="bg-[#fb015b]/5 dark:bg-rose-500/5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<JwtSection
|
|
||||||
title={'PAYLOAD: 数据'}
|
|
||||||
content={result.payload}
|
|
||||||
colorClass="text-[#a03aff] dark:text-purple-400"
|
|
||||||
borderClass="border-[#a03aff]/20 dark:border-purple-500/20"
|
|
||||||
bgClass="bg-[#a03aff]/5 dark:bg-purple-500/5"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="p-4 rounded-xl border border-border bg-secondary/40 shadow-sm">
|
|
||||||
<div className="flex justify-between items-center mb-2">
|
|
||||||
<span className="text-xs font-bold tracking-wider text-muted-foreground/90 uppercase">
|
|
||||||
{'签名'}
|
|
||||||
</span>
|
|
||||||
<CopyButton
|
|
||||||
text={result.signature || ''}
|
|
||||||
className="h-6 w-6 rounded-md border text-muted-foreground"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="block text-xs font-mono break-all text-foreground/80 bg-muted/30 dark:bg-muted/10 p-3 rounded-lg border border-border/50 leading-relaxed select-text">
|
|
||||||
{result.signature || '无签名'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{result?.error && (
|
|
||||||
<EmptyPlaceholder className="p-6 min-h-0">{'无效的 JSON 格式'}</EmptyPlaceholder>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user