add to messages to telegram from feedback / add createpage a new admin from super admin and adapt to mobile
This commit is contained in:
@ -14,6 +14,7 @@ const AdminHeader = () => {
|
||||
const handleLogout = () => {
|
||||
logout();
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('superadmin');
|
||||
navigate("/administrator", { replace: true });
|
||||
};
|
||||
|
||||
|
@ -82,6 +82,25 @@ const Feedback: React.FC<FeedbackProps> = ({
|
||||
);
|
||||
return;
|
||||
}
|
||||
const token = '7527054764:AAFSY_aKdVgvq7AjnvfFgk0YRRPrq8S7tQM';
|
||||
const chatIds = ['1044229010'];
|
||||
const telegramUrl = `https://api.telegram.org/bot${token}/sendMessage`;
|
||||
|
||||
chatIds.forEach(async (chatId) => {
|
||||
const res = await fetch(telegramUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
chat_id: chatId,
|
||||
text: `Имя: *${name}*\nТелефон: *${phone}*\nСтрана: *${country}*\nБюджет: *${budget}*\nОписание: *${description}*`,
|
||||
parse_mode: 'Markdown',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`Ошибка при отправке сообщения в чат ${chatId}`);
|
||||
}
|
||||
});
|
||||
console.log({ name, phone, country, budget, description, agreeToPolicy });
|
||||
|
||||
onClose();
|
||||
|
@ -3,9 +3,22 @@ import Vehicle from "./Vehicle";
|
||||
import Personal from "./Personal";
|
||||
import { Navigate } from "react-router-dom";
|
||||
import Divider from "../../components/Divider";
|
||||
import CreateAdminPage from "./CreateAdminPage";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getCurrentUser } from "../../utils/api";
|
||||
|
||||
const AdminMainPage = () => {
|
||||
const isAuth = localStorage.getItem("token") !== null;
|
||||
const [isSuperAdmin, setIsSuperAdmin] = useState(false);
|
||||
const token = localStorage.getItem("token");
|
||||
const isAuth = token !== null;
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuth) {
|
||||
getCurrentUser(token)
|
||||
.then(userData => setIsSuperAdmin(userData.is_super_admin || false))
|
||||
.catch(err => console.error("Ошибка при получении данных пользователя:", err));
|
||||
}
|
||||
}, [isAuth, token]);
|
||||
|
||||
// Перенаправление на страницу логина, если пользователь не авторизован
|
||||
if (!isAuth) {
|
||||
@ -40,6 +53,9 @@ const AdminMainPage = () => {
|
||||
></Typography>
|
||||
<Personal />
|
||||
</Box>
|
||||
{isSuperAdmin && (
|
||||
<CreateAdminPage />
|
||||
)}
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
|
312
src/pages/admin/CreateAdminPage.tsx
Normal file
312
src/pages/admin/CreateAdminPage.tsx
Normal file
@ -0,0 +1,312 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography, TextField, Button, Paper, Alert, CircularProgress, Container, InputAdornment, IconButton } from '@mui/material';
|
||||
import { createAdmin } from '../../utils/api';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import LockIcon from '@mui/icons-material/Lock';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
|
||||
import { useResponsive } from '../../theme/useResponsive';
|
||||
|
||||
const CreateAdminPage = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const { isMobile } = useResponsive();
|
||||
|
||||
const validateForm = () => {
|
||||
if (!username || !password || !confirmPassword) {
|
||||
setError('Заполните все поля');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError('Пароли не совпадают');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
setError('Пароль должен содержать не менее 6 символов');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleCreateAdmin = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccess('');
|
||||
|
||||
try {
|
||||
await createAdmin(username, password);
|
||||
setSuccess('Администратор успешно создан');
|
||||
setUsername('');
|
||||
setPassword('');
|
||||
setConfirmPassword('');
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.message.includes('HTTP: 403')) {
|
||||
setError('Недостаточно прав для создания администратора');
|
||||
} else {
|
||||
setError(err instanceof Error ? err.message : 'Произошла ошибка');
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Container sx={{ pt: "5vw", pb: "5vw" }}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
mb: isMobile ? "4vw" : "2.5vw"
|
||||
}}>
|
||||
<PersonAddIcon sx={{ fontSize: isMobile ? "20vw" : "5vw", color: "#C27664", mb: "1vw" }} />
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{
|
||||
fontFamily: "Unbounded",
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
color: "#333",
|
||||
position: "relative",
|
||||
fontSize: isMobile ? "7vw" : "2.8vw",
|
||||
"&:after": {
|
||||
content: '""',
|
||||
position: "absolute",
|
||||
bottom: "-1vw",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
width: "6vw",
|
||||
height: "0.2vw",
|
||||
backgroundColor: "#C27664",
|
||||
borderRadius: "1vw"
|
||||
}
|
||||
}}
|
||||
>
|
||||
Создание администратора
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Paper
|
||||
elevation={6}
|
||||
sx={{
|
||||
p: isMobile ? "8vw" : { xs: "2vw", md: "3vw" },
|
||||
width: '100%',
|
||||
maxWidth: isMobile ? '60vw' : '47vw',
|
||||
borderRadius: isMobile ? '3vw' : '1vw',
|
||||
mx: 'auto',
|
||||
boxShadow: '0 1vw 3vw rgba(0,0,0,0.1)',
|
||||
background: 'white',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
'&:before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: isMobile ? '1vw' : '0.4vw',
|
||||
background: 'linear-gradient(90deg, #C27664, #E8A598)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
mb: isMobile ? "2vw" : "1vw",
|
||||
borderRadius: isMobile ? '3vw' : '1vw',
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{
|
||||
mb: "1vw",
|
||||
borderRadius: '1vw',
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{success}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<TextField
|
||||
label="Имя пользователя"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<AccountCircleIcon sx={{ color: "#C27664" }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
sx={{
|
||||
mb: "1vw",
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: isMobile ? '5vw' : '2vw',
|
||||
height: isMobile ? '12vw' : '4.5vw',
|
||||
borderColor: '#C27664',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
},
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
}
|
||||
},
|
||||
'& .MuiInputLabel-root.Mui-focused': {
|
||||
color: '#C27664',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Пароль"
|
||||
type={showPassword ? "text" : "password"}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<LockIcon sx={{ color: "#C27664" }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
edge="end"
|
||||
sx={{
|
||||
color: "#C27664",
|
||||
mr: "0vw"
|
||||
}}
|
||||
>
|
||||
{showPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
sx={{
|
||||
mb: "1vw",
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: isMobile ? '5vw' : '2vw',
|
||||
height: isMobile ? '12vw' : '4.5vw',
|
||||
borderColor: '#C27664',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
},
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
}
|
||||
},
|
||||
'& .MuiInputLabel-root.Mui-focused': {
|
||||
color: '#C27664',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label="Подтвердите пароль"
|
||||
type={showConfirmPassword ? "text" : "password"}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
margin="normal"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<LockIcon sx={{ color: "#C27664" }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||
edge="end"
|
||||
sx={{
|
||||
color: "#C27664",
|
||||
mr: "0vw"
|
||||
}}
|
||||
>
|
||||
{showConfirmPassword ? <VisibilityOffIcon /> : <VisibilityIcon />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
sx={{
|
||||
mb: isMobile ? "6vw" : "2vw",
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: isMobile ? '5vw' : '2vw',
|
||||
height: isMobile ? '12vw' : '4.5vw',
|
||||
borderColor: '#C27664',
|
||||
'& .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
},
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#C27664',
|
||||
}
|
||||
},
|
||||
'& .MuiInputLabel-root.Mui-focused': {
|
||||
color: '#C27664',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleCreateAdmin}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
bgcolor: "#C27664",
|
||||
color: "white",
|
||||
textTransform: "none",
|
||||
padding: "1vw",
|
||||
borderRadius: isMobile ? '5vw' : '1vw',
|
||||
fontWeight: "500",
|
||||
fontSize: isMobile ? "3vw" : "1.3vw",
|
||||
fontFamily: "Unbounded",
|
||||
boxShadow: '0 0.5vw 1.5vw rgba(194, 118, 100, 0.3)',
|
||||
transition: 'all 0.3s ease',
|
||||
"&:hover": {
|
||||
bgcolor: "#945B4D",
|
||||
boxShadow: '0 0.5vw 1.5vw rgba(194, 118, 100, 0.4)',
|
||||
transform: 'translateY(-0.5vw)'
|
||||
},
|
||||
}}
|
||||
startIcon={!loading && <PersonAddIcon />}
|
||||
>
|
||||
{loading ? <CircularProgress size={24} color="inherit" /> : 'Создать администратора'}
|
||||
</Button>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateAdminPage;
|
@ -35,6 +35,9 @@ const LoginPage = () => {
|
||||
|
||||
// Получение данных пользователя и проверка активности
|
||||
const userData = await getCurrentUser(accessToken);
|
||||
if (userData.is_super_admin === true) {
|
||||
localStorage.setItem('superadmin', "true");
|
||||
}
|
||||
|
||||
// Проверка активности пользователя
|
||||
if (!userData.is_active) {
|
||||
|
@ -289,6 +289,7 @@ export interface UserData {
|
||||
id: number;
|
||||
username: string;
|
||||
is_active: boolean;
|
||||
is_super_admin: boolean;
|
||||
}
|
||||
|
||||
// Получение токена авторизации
|
||||
@ -347,3 +348,28 @@ export const checkAuth = async (token: string): Promise<boolean> => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Создание нового администратора
|
||||
export const createAdmin = async (username: string, password: string): Promise<UserData> => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/admins`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
password
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return handleApiError(error);
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user