Files
HellreigN/frontend/src/pages/registration.page.tsx
T
nikitaa_ts ed439656f8
ci-front / build (push) Successful in 2m22s
feat: add registration token
2026-04-04 05:52:43 +03:00

424 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from "react";
import { agentApiService } from "@/modules/agent/api/agent.api.service";
import { FiKey, FiPlus, FiTrash2, FiCopy, FiCheck, FiX } from "react-icons/fi";
interface RegistrationTokenForm {
label: string;
}
interface RegistrationResult {
label: string;
token: string;
}
export const RegistrationTokenPage: React.FC = () => {
const [tokens, setTokens] = useState<RegistrationTokenForm[]>([
{ label: "" },
]);
const [results, setResults] = useState<RegistrationResult[]>([]);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const handleTokenChange = (index: number, label: string) => {
const newTokens = [...tokens];
newTokens[index] = { label };
setTokens(newTokens);
};
const handleAddToken = () => {
setTokens([...tokens, { label: "" }]);
};
const handleRemoveToken = (index: number) => {
const newTokens = tokens.filter((_, i) => i !== index);
setTokens(newTokens);
};
const handleCopyToken = async (token: string, index: number) => {
await navigator.clipboard.writeText(token);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Валидация
const validTokens = tokens.filter((t) => t.label.trim());
if (validTokens.length === 0) {
setError("Введите хотя бы одну метку для токена");
return;
}
setIsSubmitting(true);
setError(null);
setSuccessMessage(null);
setResults([]);
try {
const createdTokens: RegistrationResult[] = [];
for (const tokenData of validTokens) {
const response = await agentApiService.createRegistrationToken({
label: tokenData.label,
});
// API возвращает объект с токеном
const token = response.token || Object.values(response)[0] as string;
createdTokens.push({
label: tokenData.label,
token,
});
}
setResults(createdTokens);
setSuccessMessage(
`Успешно создано ${createdTokens.length} токен(ов)`
);
setTokens([{ label: "" }]);
} catch (err) {
setError(
err instanceof Error
? err.message
: "Ошибка при создании токенов"
);
} finally {
setIsSubmitting(false);
}
};
const inputStyle: React.CSSProperties = {
width: "100%",
padding: "10px 12px",
border: "1px solid var(--border)",
borderRadius: "8px",
backgroundColor: "var(--input-bg)",
color: "var(--text-primary)",
fontSize: "14px",
transition: "border-color 0.2s, box-shadow 0.2s",
};
const labelStyle: React.CSSProperties = {
display: "block",
marginBottom: "8px",
color: "var(--text-secondary)",
fontSize: "14px",
fontWeight: 500,
};
return (
<div
className="min-h-screen py-8 px-4"
style={{ backgroundColor: "var(--bg-primary)" }}
>
<div style={{ maxWidth: "900px", margin: "0 auto" }}>
{/* Header */}
<div className="mb-8">
<div className="flex items-center gap-4 mb-4">
<div
className="w-14 h-14 rounded-xl flex items-center justify-center"
style={{ backgroundColor: "var(--bg-secondary)" }}
>
<FiKey className="w-7 h-7" style={{ color: "var(--accent)" }} />
</div>
<div>
<h1
className="text-3xl font-bold mb-1"
style={{ color: "var(--text-primary)" }}
>
Регистрация токенов для агентов
</h1>
<p style={{ color: "var(--text-secondary)", fontSize: "16px" }}>
Создайте токены для регистрации новых агентов
</p>
</div>
</div>
</div>
<form onSubmit={handleSubmit}>
{/* Token Forms */}
<div className="space-y-5">
{tokens.map((token, index) => (
<div
key={index}
className="rounded-2xl shadow-lg border"
style={{
backgroundColor: "var(--card-bg)",
borderColor: "var(--border)",
padding: "24px",
marginBottom: "20px",
}}
>
{/* Header */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "20px",
paddingBottom: "16px",
borderBottom: "1px solid var(--border)",
}}
>
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
<div
className="w-10 h-10 rounded-lg flex items-center justify-center"
style={{ backgroundColor: "var(--bg-secondary)" }}
>
<FiKey style={{ color: "var(--accent)", fontSize: "20px" }} />
</div>
<h3
style={{
margin: 0,
color: "var(--text-primary)",
fontSize: "18px",
fontWeight: 600,
}}
>
Токен #{index + 1}
</h3>
</div>
{tokens.length > 1 && (
<button
type="button"
onClick={() => handleRemoveToken(index)}
className="flex items-center gap-2 px-4 py-2 rounded-lg transition-all"
style={{
background: "var(--error-bg)",
color: "var(--error-text)",
border: "1px solid var(--error-border)",
cursor: "pointer",
fontSize: "14px",
fontWeight: 500,
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = "var(--error-text)";
e.currentTarget.style.color = "#fff";
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = "var(--error-bg)";
e.currentTarget.style.color = "var(--error-text)";
}}
>
<FiTrash2 size={14} />
Удалить
</button>
)}
</div>
{/* Label Input */}
<div>
<label style={labelStyle}>
<span
style={{ display: "flex", alignItems: "center", gap: "6px" }}
>
<FiKey size={14} />
Метка токена *
</span>
</label>
<input
type="text"
value={token.label}
onChange={(e) => handleTokenChange(index, e.target.value)}
required
style={inputStyle}
onFocus={(e) => {
e.currentTarget.style.borderColor = "var(--border-focus)";
e.currentTarget.style.boxShadow = `0 0 0 3px var(--border-focus)30`;
}}
onBlur={(e) => {
e.currentTarget.style.borderColor = "var(--border)";
e.currentTarget.style.boxShadow = "none";
}}
placeholder="agent-production-1"
/>
</div>
</div>
))}
</div>
{/* Add Token Button */}
<button
type="button"
onClick={handleAddToken}
className="w-full flex items-center justify-center gap-2 py-3.5 px-4 rounded-xl border-2 border-dashed transition-all mb-6 font-medium"
style={{
borderColor: "var(--border)",
backgroundColor: "transparent",
color: "var(--accent)",
fontSize: "15px",
cursor: "pointer",
}}
onMouseEnter={(e) => {
e.currentTarget.style.borderColor = "var(--accent)";
e.currentTarget.style.backgroundColor = "var(--accent)10";
}}
onMouseLeave={(e) => {
e.currentTarget.style.borderColor = "var(--border)";
e.currentTarget.style.backgroundColor = "transparent";
}}
>
<FiPlus size={18} />
Добавить ещё один токен
</button>
{/* Messages */}
{successMessage && (
<div
className="mb-6 p-4 rounded-lg border text-sm"
style={{
backgroundColor: "var(--success-bg)",
borderColor: "var(--success-border)",
color: "var(--success-text)",
}}
>
<div className="flex items-center justify-between">
<span>{successMessage}</span>
<button
onClick={() => setSuccessMessage(null)}
style={{ background: "none", border: "none", cursor: "pointer", color: "inherit" }}
>
<FiX size={16} />
</button>
</div>
</div>
)}
{error && (
<div
className="mb-6 p-4 rounded-lg border text-sm"
style={{
backgroundColor: "var(--error-bg)",
borderColor: "var(--error-border)",
color: "var(--error-text)",
}}
>
<div className="flex items-center justify-between">
<span>{error}</span>
<button
onClick={() => setError(null)}
style={{ background: "none", border: "none", cursor: "pointer", color: "inherit" }}
>
<FiX size={16} />
</button>
</div>
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isSubmitting}
className="w-full flex items-center justify-center gap-2 px-4 py-3.5 rounded-xl transition-all disabled:opacity-50 disabled:cursor-not-allowed font-medium text-base mb-8"
style={{
backgroundColor: isSubmitting
? "var(--bg-secondary)"
: "var(--button-primary)",
color: "var(--button-primary-text)",
boxShadow: isSubmitting
? "none"
: "0 4px 14px var(--shadow-color)",
}}
onMouseEnter={(e) => {
if (!isSubmitting) {
e.currentTarget.style.backgroundColor =
"var(--button-primary-hover)";
}
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = isSubmitting
? "var(--bg-secondary)"
: "var(--button-primary)";
}}
>
{isSubmitting ? (
<>
<div className="w-5 h-5 border-2 border-current border-t-transparent rounded-full animate-spin" />
Создание токенов...
</>
) : (
<>
<FiKey size={18} />
Создать {tokens.filter((t) => t.label.trim()).length || 1} токен(ов)
</>
)}
</button>
{/* Results */}
{results.length > 0 && (
<div>
<h2
className="text-xl font-bold mb-4"
style={{ color: "var(--text-primary)" }}
>
Созданные токены
</h2>
<div className="space-y-4">
{results.map((result, index) => (
<div
key={index}
className="rounded-xl border p-4"
style={{
backgroundColor: "var(--card-bg)",
borderColor: "var(--border)",
}}
>
<div className="flex items-center justify-between mb-3">
<span
className="font-medium"
style={{ color: "var(--text-primary)" }}
>
{result.label}
</span>
<button
type="button"
onClick={() => handleCopyToken(result.token, index)}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg transition-all text-xs font-medium"
style={{
backgroundColor:
copiedIndex === index
? "var(--success-text)"
: "var(--accent)",
color:
copiedIndex === index
? "#fff"
: "var(--accent-text)",
}}
>
{copiedIndex === index ? (
<>
<FiCheck size={12} />
Скопировано
</>
) : (
<>
<FiCopy size={12} />
Копировать
</>
)}
</button>
</div>
<code
className="block p-3 rounded-lg text-xs font-mono break-all"
style={{
backgroundColor: "var(--bg-secondary)",
color: "var(--accent)",
border: "1px solid var(--border)",
}}
>
{result.token}
</code>
</div>
))}
</div>
</div>
)}
</form>
</div>
</div>
);
};