diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index 8e93a69..72626eb 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -17,13 +17,15 @@ import { Lock as KeyIcon, Visibility as EyeIcon, VisibilityOff as EyeOffIcon, - Notifications as BellIcon + Notifications as BellIcon, + IntegrationInstructions as IntegrationIcon } from "@mui/icons-material"; import AccountProfile from "../../components/AccountProfile"; import AccountSecurity from "../../components/AccountSecurity"; import AccountNotifications from "../../components/AccountNotifications"; import AccountAgentTransactionSection from "../../components/AccountAgentTransactionSection"; import TabsNav from "../../components/TabsNav"; +import AccountIntegration from "../../components/AccountIntegration"; const initialNotifications = { emailNotifications: true, @@ -48,6 +50,7 @@ export default function AccountPage() { { id: "security", label: "Безопасность", icon: }, { id: "notifications", label: "Уведомления", icon: }, { id: "agent-transactions", label: "Транзакции агентов", icon: }, + { id: "integration", label: "Интеграции", icon: }, ]; return ( @@ -69,6 +72,9 @@ export default function AccountPage() { {activeTab === "agent-transactions" && ( )} + {activeTab === "integration" && ( + + )} ); diff --git a/src/components/AccountIntegration.tsx b/src/components/AccountIntegration.tsx new file mode 100644 index 0000000..d1f1d04 --- /dev/null +++ b/src/components/AccountIntegration.tsx @@ -0,0 +1,121 @@ +"use client"; +import React, { useState, useMemo } from "react"; +import { + Box, + Button, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + IconButton +} from "@mui/material"; +import CreateTokenDialog from "./CreateTokenDialog"; +import IntegrationTokensTable from "./IntegrationTokensTable"; +import styles from "../styles/account.module.css"; + +// Компонент для управления интеграциями и токенами +interface Token { + id: string; + description: string; + token: string; + createdAt: string; + lastUsedAt?: string; +} + +const AccountIntegration = () => { + const [tokens, setTokens] = useState([]); + const [openCreateDialog, setOpenCreateDialog] = useState(false); + const [tokenDescription, setTokenDescription] = useState(""); + const [showTokenWarning, setShowTokenWarning] = useState(false); + const [showTokenCreatedSuccess, setShowTokenCreatedSuccess] = useState(false); + const [openEditDialog, setOpenEditDialog] = useState(false); + const [editingToken, setEditingToken] = useState(null); + + const handleOpenCreateDialog = () => { + setOpenCreateDialog(true); + setTokenDescription(""); + setShowTokenWarning(false); + }; + + const handleCloseCreateDialog = () => { + setOpenCreateDialog(false); + }; + + const handleOpenEditDialog = (token: Token) => { + setEditingToken(token); + setOpenEditDialog(true); + }; + + const handleCloseEditDialog = () => { + setOpenEditDialog(false); + setEditingToken(null); + }; + + const handleTokenGenerate = (description: string, newTokenValue: string) => { + const newToken: Token = { + id: (tokens.length + 1).toString(), + description: description, + token: newTokenValue, + createdAt: new Date().toLocaleString(), + lastUsedAt: "Никогда", + }; + setTokens([...tokens, newToken]); + setShowTokenCreatedSuccess(true); + setTimeout(() => setShowTokenCreatedSuccess(false), 1500); + }; + + const handleTokenUpdate = (updatedDescription: string) => { + if (editingToken) { + setTokens(prevTokens => + prevTokens.map(token => + token.id === editingToken.id ? { ...token, description: updatedDescription } : token + ) + ); + setOpenEditDialog(false); + setEditingToken(null); + } + }; + + const handleDeleteToken = (tokenId: string) => { + setTokens(prevTokens => prevTokens.filter(token => token.id !== tokenId)); + }; + + return ( + + + Управление токенами интеграции + + + + + + + + + {showTokenCreatedSuccess && ( +
+ Токен успешно создан! +
+ )} +
+ ); +}; + +export default AccountIntegration; \ No newline at end of file diff --git a/src/components/CreateTokenDialog.tsx b/src/components/CreateTokenDialog.tsx new file mode 100644 index 0000000..925d39e --- /dev/null +++ b/src/components/CreateTokenDialog.tsx @@ -0,0 +1,157 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { + Box, + Button, + TextField, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Snackbar, + Alert, +} from "@mui/material"; +import { ContentCopy as ContentCopyIcon } from "@mui/icons-material"; +import styles from "../styles/account.module.css"; + +interface CreateTokenDialogProps { + open: boolean; + onClose: () => void; + onTokenGenerate?: (description: string, token: string) => void; + isEditMode?: boolean; + initialDescription?: string; + onTokenUpdate?: (updatedDescription: string) => void; +} + +const CreateTokenDialog: React.FC = ({ + open, + onClose, + onTokenGenerate, + isEditMode, + initialDescription, + onTokenUpdate, +}) => { + const [tokenDescription, setTokenDescription] = useState(""); + const [generatedToken, setGeneratedToken] = useState(""); + const [showTokenWarning, setShowTokenWarning] = useState(false); + const [showCopySuccess, setShowCopySuccess] = useState(false); + + useEffect(() => { + if (!open) { + setTokenDescription(""); + setGeneratedToken(""); + setShowTokenWarning(false); + setShowCopySuccess(false); + } else if (isEditMode && initialDescription) { + setTokenDescription(initialDescription); + } else { + setTokenDescription(""); + setGeneratedToken(""); + setShowTokenWarning(false); + setShowCopySuccess(false); + } + }, [open, isEditMode, initialDescription]); + + const handleGenerateToken = () => { + if (tokenDescription.trim() === "") return; + + const newTokenValue = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); + setGeneratedToken(newTokenValue); + setShowTokenWarning(true); + if (onTokenGenerate) { + onTokenGenerate(tokenDescription, newTokenValue); + } + }; + + const handleUpdateDescription = () => { + if (tokenDescription.trim() === "") return; + if (onTokenUpdate) { + onTokenUpdate(tokenDescription); + } + onClose(); + }; + + const handleCopy = () => { + if (!generatedToken) return; + if (typeof navigator !== "undefined" && navigator.clipboard) { + navigator.clipboard.writeText(generatedToken); + } else { + const textarea = document.createElement("textarea"); + textarea.value = generatedToken; + document.body.appendChild(textarea); + textarea.select(); + document.execCommand("copy"); + document.body.removeChild(textarea); + } + setShowCopySuccess(true); + setTimeout(() => setShowCopySuccess(false), 1500); + }; + + return ( + + {isEditMode ? "Редактировать описание токена" : "Создать новый токен"} + + {isEditMode || !generatedToken ? ( + <> + + {isEditMode ? "Введите новое описание для токена." : "Пожалуйста, введите описание для вашего нового токена."} + + setTokenDescription(e.target.value)} + /> + + ) : ( + + Ваш новый токен: + + + {generatedToken} + + + + {showTokenWarning && ( + + Внимание: Этот токен не может быть восстановлен. Пожалуйста, скопируйте его сейчас! + + )} + {showCopySuccess && ( +
+ Токен скопирован! +
+ )} +
+ )} +
+ + {!(isEditMode || generatedToken) ? ( + + ) : null} + + +
+ ); +}; + +export default CreateTokenDialog; \ No newline at end of file diff --git a/src/components/IntegrationTokensTable.tsx b/src/components/IntegrationTokensTable.tsx new file mode 100644 index 0000000..666ca81 --- /dev/null +++ b/src/components/IntegrationTokensTable.tsx @@ -0,0 +1,129 @@ +"use client"; +import React, { useMemo } from "react"; +import { + Box, + Button, + Typography, + IconButton +} from "@mui/material"; +import { MaterialReactTable, type MRT_ColumnDef, useMaterialReactTable } from "material-react-table"; +import { Add as AddIcon, Edit as EditIcon, Delete as DeleteIcon } from "@mui/icons-material"; + +interface Token { + id: string; + description: string; + token: string; + createdAt: string; + lastUsedAt?: string; +} + +interface IntegrationTokensTableProps { + tokens: Token[]; + onOpenCreateDialog: () => void; + onOpenEditDialog: (token: Token) => void; + onDeleteToken: (tokenId: string) => void; +} + +const IntegrationTokensTable: React.FC = ({ + tokens, + onOpenCreateDialog, + onOpenEditDialog, + onDeleteToken, +}) => { + const columns = useMemo[]>( + () => [ + { + accessorKey: "description", + header: "Описание", + size: 200, + Cell: ({ renderedCellValue }) => renderedCellValue, + }, + { + accessorKey: "token", + header: "Токен", + size: 250, + Cell: ({ renderedCellValue }) => { + const tokenValue = renderedCellValue as string; + const maskedToken = tokenValue.substring(0, 5) + "***********************" + tokenValue.substring(tokenValue.length - 4); + return ( + + {maskedToken} + + ); + }, + }, + { + accessorKey: "createdAt", + header: "Дата создания", + size: 150, + Cell: ({ renderedCellValue }) => renderedCellValue, + }, + { + accessorKey: "lastUsedAt", + header: "Последнее использование", + size: 150, + Cell: ({ renderedCellValue }) => renderedCellValue, + }, + { + id: 'actions', + header: 'Действия', + size: 100, + Cell: ({ row }) => ( + + onOpenEditDialog(row.original)} + color="primary" + > + + + onDeleteToken(row.original.id)} + color="error" + > + + + + ), + }, + ], + [onOpenEditDialog, onDeleteToken], + ); + + const table = useMaterialReactTable({ + columns, + data: tokens, + enableColumnActions: false, + enableColumnFilters: true, + enablePagination: true, + enableSorting: true, + enableBottomToolbar: true, + enableTopToolbar: true, + enableDensityToggle: true, + enableGlobalFilter: true, + enableHiding: true, + renderEmptyRowsFallback: () => ( + + + У вас пока нет созданных токенов. + + + ), + muiTableBodyCellProps: { sx: { fontSize: 14 } }, + muiTableHeadCellProps: { sx: { fontWeight: 700 } }, + initialState: { pagination: { pageSize: 10, pageIndex: 0 } }, + renderTopToolbarCustomActions: () => ( + + ), + }); + + return ; +}; + +export default IntegrationTokensTable; \ No newline at end of file