api файл и отображение списка

This commit is contained in:
Nastya 2025-06-01 17:05:41 +03:00
parent 6273e62e66
commit c2d79c04cc
5 changed files with 805 additions and 80 deletions

577
api-docs.html Normal file

@ -0,0 +1,577 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QUIZ Service API Documentation</title>
<style>
:root {
--primary-color: #7E2AEA;
--text-color: #333;
--bg-color: #fff;
--border-color: #e0e0e0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: var(--text-color);
margin: 0;
padding: 0;
background: var(--bg-color);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background: var(--primary-color);
color: white;
padding: 2rem 0;
margin-bottom: 2rem;
}
h1, h2, h3 {
color: var(--primary-color);
}
.endpoint {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
background: #fafafa;
}
.method {
display: inline-block;
padding: 4px 8px;
border-radius: 4px;
color: white;
font-weight: bold;
margin-right: 10px;
}
.get { background: #61affe; }
.post { background: #49cc90; }
.put { background: #fca130; }
.delete { background: #f93e3e; }
.patch { background: #50e3c2; }
.schema {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin: 10px 0;
}
.nav {
position: sticky;
top: 0;
background: white;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.nav a {
color: var(--primary-color);
text-decoration: none;
margin-right: 20px;
}
.nav a:hover {
text-decoration: underline;
}
code {
background: #f1f1f1;
padding: 2px 4px;
border-radius: 4px;
font-family: 'Courier New', Courier, monospace;
}
.response {
margin-top: 10px;
padding: 10px;
background: #f8f9fa;
border-left: 4px solid var(--primary-color);
}
.components {
margin: 2rem 0;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
}
.security {
background: #fff3cd;
padding: 1rem;
border-radius: 4px;
margin: 1rem 0;
}
.parameter {
margin: 0.5rem 0;
padding: 0.5rem;
background: #f8f9fa;
border-radius: 4px;
}
.parameter.required {
border-left: 4px solid #dc3545;
}
.enum-values {
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<header>
<div class="container">
<h1>QUIZ Service API Documentation</h1>
<p>Version 1.0.0</p>
</div>
</header>
<nav class="nav">
<div class="container">
<a href="#components">Components</a>
<a href="#quiz">Quiz Endpoints</a>
<a href="#question">Question Endpoints</a>
<a href="#results">Results Endpoints</a>
<a href="#statistics">Statistics Endpoints</a>
<a href="#account">Account Endpoints</a>
</div>
</nav>
<main class="container">
<section id="components">
<h2>Components</h2>
<div class="components">
<h3>Quiz Model</h3>
<div class="schema">
<pre><code>{
"id": integer, // Id of created quiz
"qid": string, // string id for customers
"deleted": boolean, // true if quiz deleted
"archived": boolean, // true if quiz archived
"fingerprinting": boolean, // set true for save deviceId
"repeatable": boolean, // set true for allow user to repeat quiz
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
"mail_notifications": boolean, // set true for mail notification for each quiz passing
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
"name": string, // name of quiz. max 280 length
"description": string, // description of quiz
"config": string, // config of quiz. serialized json for rules of quiz flow
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
"limit": integer, // limit is count of max quiz passing
"due_to": integer, // last time when quiz is valid. timestamp in seconds
"time_of_passing": integer, // seconds to pass quiz
"pausable": boolean, // true if it is allowed for pause quiz
"version": integer, // version of quiz
"version_comment": string, // version comment to version of quiz
"parent_ids": integer[], // array of previous versions of quiz
"created_at": string, // time of creating
"updated_at": string, // time of last updating
"question_cnt": integer, // count of questions
"passed_count": integer, // count passings
"average_time": integer, // average time of passing
"super": boolean, // set true if squiz realize group functionality
"group_id": integer // group of new quiz
}</code></pre>
</div>
</div>
<div class="components">
<h3>Question Model</h3>
<div class="schema">
<pre><code>{
"id": integer, // Id of created question
"quiz_id": integer, // relation to quiz
"title": string, // title of question. max 512 length
"description": string, // description of question
"type": string, // status of question. allow only text, select, file, variant, images, varimg, emoji, date, number, page, rating
"required": boolean, // user must pass this question
"deleted": boolean, // true if question is deleted
"page": integer, // page if question
"content": string, // serialized json of created question
"version": integer, // version of quiz
"parent_ids": integer[], // array of previous versions of quiz
"created_at": string, // time of creating
"updated_at": string // time of last updating
}</code></pre>
</div>
</div>
<div class="components">
<h3>Answer Model</h3>
<div class="schema">
<pre><code>{
"Id": integer, // id ответа
"Content": string, // контент ответа
"QuestionId": integer, // id вопроса к которому ответ
"QuizId": integer, // id опроса к которому ответ
"Fingerprint": string, // fingerprint
"Session": string, // сессия
"Result": boolean, // true or false?
"CreatedAt": string, // таймшап когда ответ создан
"New": boolean, // новый ответ?
"Deleted": boolean // удален?
}</code></pre>
</div>
</div>
<div class="components">
<h3>LeadTarget Model</h3>
<div class="schema">
<pre><code>{
"ID": integer, // primary key
"AccountID": string, // account identifier
"Type": string, // type of target (mail, telegram, whatsapp)
"QuizID": integer, // ID of the quiz
"Target": string, // target address
"InviteLink": string, // invitation link
"Deleted": boolean, // is deleted
"CreatedAt": string // creation timestamp
}</code></pre>
</div>
</div>
<div class="components">
<h3>TgAccount Model</h3>
<div class="schema">
<pre><code>{
"ID": integer, // primary key
"ApiID": integer, // Telegram API ID
"ApiHash": string, // Telegram API Hash
"PhoneNumber": string, // phone number
"Password": string, // account password
"Status": string, // account status (active, inactive, ban)
"Deleted": boolean, // is deleted
"CreatedAt": string // creation timestamp
}</code></pre>
</div>
</div>
</section>
<section id="quiz">
<h2>Quiz Endpoints</h2>
<div class="endpoint">
<h3>Create Quiz</h3>
<span class="method post">POST</span>
<code>/quiz/create</code>
<p>Create a new quiz with specified parameters.</p>
<div class="security">
<h4>Security</h4>
<p>This endpoint requires authentication.</p>
</div>
<h4>Request Body:</h4>
<div class="schema">
<pre><code>{
"fingerprinting": boolean, // set true for save deviceId
"repeatable": boolean, // set true for allow user to repeat quiz
"note_prevented": boolean, // set true for save statistic of incomplete quiz passing
"mail_notifications": boolean, // set true for mail notification for each quiz passing
"unique_answers": boolean, // set true for save statistics only for unique quiz passing
"name": string, // name of quiz. max 280 length
"description": string, // description of quiz
"config": string, // config of quiz. serialized json for rules of quiz flow
"status": string, // status of quiz. allow only '', 'draft', 'template', 'stop', 'start'
"limit": integer, // limit is count of max quiz passing
"due_to": integer, // last time when quiz is valid. timestamp in seconds
"time_of_passing": integer, // seconds to pass quiz
"pausable": boolean, // true if it is allowed for pause quiz
"question_cnt": integer, // count of questions
"super": boolean, // set true if squiz realize group functionality
"group_id": integer // group of new quiz
}</code></pre>
</div>
<h4>Responses:</h4>
<div class="response">
<h5>201 Created</h5>
<p>Quiz successfully created. Returns the created quiz object.</p>
<div class="schema">
<pre><code>{
"id": integer,
"qid": string,
"name": string,
"description": string,
// ... other quiz properties
}</code></pre>
</div>
</div>
<div class="response">
<h5>422 Unprocessable Entity</h5>
<p>Name field should have less than 280 characters.</p>
</div>
<div class="response">
<h5>406 Not Acceptable</h5>
<p>Status on creating must be only draft, template, stop, start or due to time must be lesser than now.</p>
<div class="enum-values">
Allowed status values: '', 'draft', 'template', 'stop', 'start'
</div>
</div>
<div class="response">
<h5>409 Conflict</h5>
<p>You can pause quiz only if it has deadline for passing.</p>
</div>
<div class="response">
<h5>500 Internal Server Error</h5>
<p>If you get any content string send it to developer.</p>
</div>
</div>
<div class="endpoint">
<h3>Get Quiz List</h3>
<span class="method post">POST</span>
<code>/quiz/getList</code>
<p>Get paginated list of quizzes with filtering options.</p>
<h4>Request Body:</h4>
<div class="schema">
<pre><code>{
"limit": integer,
"offset": integer,
"from": integer,
"to": integer,
"search": string,
"status": string,
"deleted": boolean,
"archived": boolean,
"super": boolean,
"group_id": integer
}</code></pre>
</div>
<h4>Responses:</h4>
<div class="response">
<h5>200 OK</h5>
<p>Returns list of quizzes with total count.</p>
<div class="schema">
<pre><code>{
"count": integer,
"items": [
{
"id": integer,
"qid": string,
// ... other quiz properties
}
]
}</code></pre>
</div>
</div>
<div class="response">
<h5>406 Not Acceptable</h5>
<p>Inappropriate status, allowed only '', 'stop', 'start', 'draft', 'template', 'timeout', 'offlimit'.</p>
</div>
<div class="response">
<h5>500 Internal Server Error</h5>
<p>If you get any content string send it to developer.</p>
</div>
</div>
<!-- Add more quiz endpoints -->
</section>
<section id="question">
<h2>Question Endpoints</h2>
<div class="endpoint">
<h3>Create Question</h3>
<span class="method post">POST</span>
<code>/question/create</code>
<p>Create a new question for a quiz.</p>
</div>
<!-- Add more question endpoints -->
</section>
<section id="results">
<h2>Results Endpoints</h2>
<div class="endpoint">
<h3>Get Quiz Results</h3>
<span class="method post">POST</span>
<code>/results/getResults/{quizId}</code>
<p>Get list of quiz results with pagination.</p>
<h4>Path Parameters:</h4>
<div class="parameter">
<code>quizId</code> - ID of the quiz to get results for
</div>
<h4>Request Body:</h4>
<div class="schema">
<pre><code>{
"to": integer, // таймштамп времени, до которого выбирать статистику. если 0 или не передано - этого ограничения нет
"from": integer, // таймштамп времени, после которого выбирать статистику. если 0 или не передано - этого ограничения нет
"new": boolean, // флаг, по которому вернутся только новые результаты, ещё не просмотренные пользователем
"page": integer, // номер страницы для пагинации
"limit": integer // размер страницы для пагинации
}</code></pre>
</div>
<h4>Responses:</h4>
<div class="response">
<h5>200 OK</h5>
<p>Returns paginated list of results.</p>
<div class="schema">
<pre><code>{
"total_count": integer, // общее количество элементов удволетворяющее фильтру
"results": [
{
"content": string, // содержимое ответа
"id": integer, // айдишник ответа
"new": boolean, // статус, был ли просмотрен ответ
"created_at": string // время создания этого результата
}
]
}</code></pre>
</div>
</div>
</div>
<div class="endpoint">
<h3>Export Results</h3>
<span class="method post">POST</span>
<code>/results/{quizID}/export</code>
<p>Export quiz results to CSV format.</p>
<h4>Path Parameters:</h4>
<div class="parameter required">
<code>quizID</code> - ID of the quiz to export results from
</div>
<h4>Request Body:</h4>
<div class="schema">
<pre><code>{
"to": string, // Дата окончания диапазона времени для экспорта результатов
"from": string, // Дата начала временного диапазона для экспорта результатов
"new": boolean // Экспортировать ли только новые результаты?
}</code></pre>
</div>
<h4>Responses:</h4>
<div class="response">
<h5>200 OK</h5>
<p>Returns CSV file with quiz results.</p>
<div class="schema">
<pre><code>Content-Type: text/csv</code></pre>
</div>
</div>
</div>
</section>
<section id="telegram">
<h2>Telegram Endpoints</h2>
<div class="endpoint">
<h3>Create Telegram Account</h3>
<span class="method post">POST</span>
<code>/telegram/create</code>
<p>Authorize server in Telegram account.</p>
<h4>Request Body:</h4>
<div class="schema">
<pre><code>{
"ApiID": integer, // Telegram API ID
"ApiHash": string, // Telegram API Hash
"PhoneNumber": string, // Phone number
"Password": string // Account password
}</code></pre>
</div>
<h4>Responses:</h4>
<div class="response">
<h5>200 OK</h5>
<p>Returns signature for code verification.</p>
<div class="schema">
<pre><code>{
"signature": string // Session identifier for code verification
}</code></pre>
</div>
</div>
<div class="response">
<h5>409 Conflict</h5>
<p>Account already exists and is active.</p>
</div>
</div>
</section>
<section id="audience">
<h2>Audience Endpoints</h2>
<div class="endpoint">
<h3>Create Quiz Audience</h3>
<span class="method post">POST</span>
<code>/quiz/{quizID}/auditory</code>
<p>Create audience for a quiz.</p>
<h4>Path Parameters:</h4>
<div class="parameter required">
<code>quizID</code> - ID of the quiz
</div>
<h4>Responses:</h4>
<div class="response">
<h5>200 OK</h5>
<p>Returns ID of created audience.</p>
<div class="schema">
<pre><code>{
"id": integer // ID of created auditory
}</code></pre>
</div>
</div>
</div>
</section>
<section id="statistics">
<h2>Statistics Endpoints</h2>
<div class="endpoint">
<h3>Get Question Statistics</h3>
<span class="method post">POST</span>
<code>/statistic/{quizID}/questions</code>
<p>Get statistics for specific questions in a quiz.</p>
</div>
<!-- Add more statistics endpoints -->
</section>
<section id="account">
<h2>Account Endpoints</h2>
<div class="endpoint">
<h3>Add Lead Target</h3>
<span class="method post">POST</span>
<code>/account/leadtarget</code>
<p>Add a target destination for lead notifications.</p>
</div>
<!-- Add more account endpoints -->
</section>
</main>
<footer class="container">
<p>© 2024 QUIZ Service API Documentation</p>
</footer>
</body>
</html>

106
src/api/auditory.ts Normal file

@ -0,0 +1,106 @@
import { makeRequest } from "@frontend/kitui";
import { parseAxiosError } from "@utils/parse-error";
const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz`;
// Types
export interface AuditoryItem {
id: number;
quiz_id: number;
sex: boolean;
age: string;
deleted: boolean;
created_at: number;
}
export interface AuditoryResponse {
data: AuditoryItem[];
}
// Request Types
export interface AuditoryGetRequest {
quizId: number;
}
export interface AuditoryDeleteRequest {
id: number;
}
export interface AuditoryAddRequest {
sex: boolean;
age: string;
}
// Parameters
export interface AuditoryGetParams {
quizId: number;
}
export interface AuditoryDeleteParams {
quizId: number;
auditoryId: number;
}
export interface AuditoryAddParams {
quizId: number;
body: AuditoryAddRequest;
}
// API calls
export const auditoryGet = async ({ quizId }: AuditoryGetParams): Promise<[AuditoryResponse | null, string?]> => {
if (!quizId) {
return [null, "Quiz ID is required"];
}
try {
const response = await makeRequest<AuditoryGetRequest, AuditoryResponse>({
url: `${API_URL}/quiz/${quizId}/auditory`,
method: "GET",
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось получить аудиторию. ${error}`];
}
};
export const auditoryDelete = async ({ quizId, auditoryId }: AuditoryDeleteParams): Promise<[AuditoryResponse | null, string?]> => {
if (!quizId || !auditoryId) {
return [null, "Quiz ID and Auditory ID are required"];
}
try {
const response = await makeRequest<AuditoryDeleteRequest, AuditoryResponse>({
url: `${API_URL}/quiz/${quizId}/auditory`,
body: {
id: auditoryId
},
method: "DELETE",
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось удалить аудиторию. ${error}`];
}
};
export const auditoryAdd = async ({ quizId, body }: AuditoryAddParams): Promise<[AuditoryResponse | null, string?]> => {
if (!quizId) {
return [null, "Quiz ID is required"];
}
try {
const response = await makeRequest<AuditoryAddRequest, AuditoryResponse>({
url: `${API_URL}/quiz/${quizId}/auditory`,
body,
method: "POST",
});
return [response];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Не удалось добавить аудиторию. ${error}`];
}
};

@ -148,8 +148,8 @@ export const addQuizImages = async (
const name = image?.name ? transliterate(image?.name.replace(/\s/g, '_')) : "blob" const name = image?.name ? transliterate(image?.name.replace(/\s/g, '_')) : "blob"
//Замена всех побелов на _ //Замена всех побелов на _
const renamedImage = new File([image], name) const renamedImage = new File([image], name)
formData.append("quiz", quizId.toString()); formData.append("quiz", quizId.toString());
formData.append("image", renamedImage); formData.append("image", renamedImage);

@ -0,0 +1,114 @@
import { auditoryGet, AuditoryResponse, AuditoryItem } from "@/api/auditory";
import ArrowDownIcon from "@/assets/icons/ArrowDownIcon";
import CopyIcon from "@/assets/icons/CopyIcon";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { InfoPopover } from "@/ui_kit/InfoPopover";
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
import { Box, Collapse, IconButton, List, ListItem, Typography, useTheme } from "@mui/material";
import { useEffect, useState } from "react";
const PURPLE = "#7E2AEA";
export const AuditoryList = () => {
const theme = useTheme();
const quiz = useCurrentQuiz();
const { isTestServer } = useDomainDefine();
const [linksOpen, setLinksOpen] = useState(true);
const [auditory, setAuditory] = useState<AuditoryItem[]>([]);
const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);
};
useEffect(() => {
(async () => {
if (quiz?.backendId) {
const [result, error] = await auditoryGet({ quizId: quiz.backendId });
console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__")
console.log(result)
if (result) {
setAuditory(result);
}
}
})();
}, [quiz]);
console.log("auditory-___---_auditory__---__-__auditory_------__---__-__---_------__---__-__---_------__---__-____--__")
console.log(auditory)
return (
<>
<Box sx={{
maxWidth: "796px",
bgcolor: "#fff",
borderRadius: "12px",
p: "20px",
boxShadow: "0px 4px 32px 0px #7E2AEA14",
mt: "24px"
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography sx={{ fontSize: "18px", fontWeight: 500, color: theme.palette.grey3.main }}>
Ваши сохраненные ссылки
</Typography>
<IconButton
sx={{ cursor: 'pointer', color: PURPLE, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
onClick={() => setLinksOpen((prev) => !prev)}
size="large"
>
<ArrowDownIcon style={{ width: "18px", height: "18px" }} />
</IconButton>
</Box>
<Collapse in={linksOpen} timeout="auto" unmountOnExit sx={{ mt: "3px" }}>
<List sx={{ gap: '8px', p: 0, m: 0 }}>
{auditory.map((item, idx) => {
const linkText = `${isTestServer ? "https://s.hbpn.link/" : "https://hbpn.link/"}?_paud=${item.id}`;
console.log(item)
return (
<ListItem
key={idx}
disablePadding
sx={{
bgcolor: "#F2F3F7",
borderRadius: "10px",
p: "13px 14px 13px 20px",
mb: "8px",
maxWidth: "756px",
display: "flex",
alignItems: "center",
transition: 'background 0.2s, border 0.2s',
'& .MuiListItemSecondaryAction-root': {
display: 'flex',
alignItems: 'center',
gap: '12px',
width: "60px",
justifyContent: "space-between",
},
}}
secondaryAction={
<>
<IconButton edge="end" aria-label="info" sx={{ color: PURPLE, p: 0, width: 18, height: 18 }}>
<InfoPopover />
</IconButton>
<IconButton
edge="end"
aria-label="copy"
sx={{ color: PURPLE, p: 0, width: 18, height: 18, marginRight: "-2px" }}
onClick={() => handleCopy(linkText)}
>
<CopyIcon color={PURPLE} />
</IconButton>
</>
}
>
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
{linkText}
</Typography>
</ListItem>
);
})}
</List>
</Collapse>
</Box>
</>
);
};

@ -2,24 +2,20 @@ import { Box, Container, Typography, TextField, Button, List, ListItem, IconButt
import { InfoPopover } from '@ui_kit/InfoPopover'; import { InfoPopover } from '@ui_kit/InfoPopover';
import CopyIcon from "@/assets/icons/CopyIcon"; import CopyIcon from "@/assets/icons/CopyIcon";
import GenderAndAgeSelector from "./GenderAndAgeSelector"; import GenderAndAgeSelector from "./GenderAndAgeSelector";
import { useState } from "react"; import { useEffect, useState } from "react";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import { ArrowDownIcon } from "../../assets/icons/questionsPage/ArrowDownIcon"; import { ArrowDownIcon } from "../../assets/icons/questionsPage/ArrowDownIcon";
import { useTheme } from "@mui/material"; import { useTheme } from "@mui/material";
import { auditoryAdd, auditoryGet } from "@/api/auditory";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { AuditoryList } from "./AuditoryList";
const PURPLE = "#7E2AEA"; const PURPLE = "#7E2AEA";
const GREY_TEXT = "#A0A0A0";
const GREY_BORDER = "#E0E0E0";
const GREY_ICON = "#B0B0B0";
const BLOCK_RADIUS = "16px";
const BLOCK_PX = "32px";
const BLOCK_PY = "24px";
export default function PersonalizationAI() { export default function PersonalizationAI() {
const [gender, setGender] = useState('');
const [linksOpen, setLinksOpen] = useState(true);
const theme = useTheme(); const theme = useTheme();
const [gender, setGender] = useState('');
return ( return (
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}> <Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}>
@ -47,9 +43,6 @@ export default function PersonalizationAI() {
}}> }}>
<GenderAndAgeSelector gender={gender} setGender={setGender} /> <GenderAndAgeSelector gender={gender} setGender={setGender} />
{/* Ссылка */} {/* Ссылка */}
<Box sx={{ mt: "34px" }}> <Box sx={{ mt: "34px" }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
@ -95,73 +88,8 @@ export default function PersonalizationAI() {
</Box> </Box>
</Box> </Box>
{/* Второй белый блок */} <AuditoryList />
<Box sx={{
maxWidth: "796px",
bgcolor: "#fff",
borderRadius: "12px",
p: "20px",
boxShadow: "0px 4px 32px 0px #7E2AEA14",
mt: "24px"
}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography sx={{ fontSize: "18px", fontWeight: 500, color: theme.palette.grey3.main }}>
Ваши сохраненные ссылки
</Typography>
<IconButton
sx={{ cursor: 'pointer', color: PURPLE, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
onClick={() => setLinksOpen((prev) => !prev)}
size="large"
>
<ArrowDownIcon style={{ width: "18px", height: "18px" }} />
</IconButton>
</Box>
<Collapse in={linksOpen} timeout="auto" unmountOnExit sx={{ mt: "3px" }}>
<List sx={{ gap: '8px', p: 0, m: 0 }}>
{[1, 2, 3, 4, 5].map((_, idx) => (
<ListItem
key={idx}
disablePadding
sx={{
bgcolor: "#F2F3F7",
borderRadius: "10px",
p: "13px 14px 13px 20px",
mb: "8px",
maxWidth: "756px",
display: "flex",
alignItems: "center",
transition: 'background 0.2s, border 0.2s',
'& .MuiListItemSecondaryAction-root': {
display: 'flex',
alignItems: 'center',
gap: '12px',
width: "60px",
justifyContent: "space-between",
},
}}
secondaryAction={
<>
<IconButton edge="end" aria-label="info" sx={{ color: PURPLE, p: 0, width: 18, height: 18 }}>
<InfoPopover />
</IconButton>
<IconButton edge="end" aria-label="copy" sx={{ color: PURPLE, p: 0, width: 18, height: 18, marginRight: "-2px" }}>
<CopyIcon
color={PURPLE}
/>
</IconButton>
</>
}
>
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
linkexample.ru
</Typography>
</ListItem>
))}
</List>
</Collapse>
</Box>
</Container> </Container>
); );
} }