add promo to News

This commit is contained in:
2025-12-20 21:12:52 +05:00
parent f8b358d9bd
commit 779f8f779d

View File

@ -14,6 +14,14 @@ import { fetchNews, NewsItem, createNews, fetchMe, deleteNews } from '../api';
import { FullScreenLoader } from '../components/FullScreenLoader';
import { MarkdownEditor } from '../components/MarkdownEditor';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import React from 'react';
import CustomNotification, {
NotificationPosition,
} from '../components/Notifications/CustomNotification';
import {
getNotifPositionFromSettings,
isNotificationsEnabled,
} from '../utils/notifications';
export const News = () => {
const [news, setNews] = useState<NewsItem[]>([]);
@ -31,6 +39,17 @@ export const News = () => {
const [preview, setPreview] = useState('');
const [markdown, setMarkdown] = useState('');
const [notifOpen, setNotifOpen] = useState(false);
const [notifMsg, setNotifMsg] = useState<React.ReactNode>('');
const [notifSeverity, setNotifSeverity] = useState<
'success' | 'info' | 'warning' | 'error'
>('info');
const [notifPos, setNotifPos] = useState<NotificationPosition>({
vertical: 'bottom',
horizontal: 'center',
});
const [isAdmin, setIsAdmin] = useState(false);
useEffect(() => {
@ -103,6 +122,18 @@ export const News = () => {
}
};
const showNotification = (
message: React.ReactNode,
severity: 'success' | 'info' | 'warning' | 'error' = 'info',
position: NotificationPosition = getNotifPositionFromSettings(),
) => {
if (!isNotificationsEnabled()) return;
setNotifMsg(message);
setNotifSeverity(severity);
setNotifPos(position);
setNotifOpen(true);
};
const handleDeleteNews = async (id: string) => {
const confirmed = window.confirm('Точно удалить эту новость?');
if (!confirmed) return;
@ -148,6 +179,69 @@ export const News = () => {
);
}
const PromoInline = ({ code }: { code: string }) => {
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(code);
showNotification(`Промокод ${code} скопирован`, 'success');
} catch {
// fallback для старых браузеров
const textarea = document.createElement('textarea');
textarea.value = code;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
showNotification(`Промокод ${code} скопирован`, 'success');
}
};
return (
<Box
component="span"
onClick={handleCopy}
title="Нажмите, чтобы скопировать промокод"
sx={{
fontFamily: 'Benzin-Bold',
backgroundImage:
'linear-gradient(71deg, #F27121 0%, #E940CD 60%, #8A2387 100%)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
px: '0.2em',
whiteSpace: 'nowrap',
cursor: 'pointer', // 👈 показывает интерактивность
userSelect: 'none',
'&:hover': {
filter: 'brightness(1.15)',
},
'&:active': {
transform: 'scale(0.97)',
},
transition: 'all 0.15s ease',
}}
>
{code}
</Box>
);
};
const renderWithPromoCodes = (text: string) => {
const parts = text.split(/(\/\/[A-Z0-9-_]+)/g);
return parts.map((part, i) => {
if (part.startsWith('//')) {
const code = part.slice(2);
return <PromoInline key={i} code={code} />;
}
return part;
});
};
return (
<Box
sx={{
@ -196,7 +290,7 @@ export const News = () => {
'& .MuiInputBase-root': {
backgroundColor: 'rgba(0,0,0,0.6)',
color: 'white',
borderRadius: '1.2vw'
borderRadius: '1.2vw',
},
'& .MuiInputLabel-root': {
color: 'rgba(255,255,255,0.7)',
@ -230,21 +324,24 @@ export const News = () => {
backgroundColor: 'rgba(0,0,0,0.6)',
borderRadius: '1.2vw',
overflow: 'hidden',
border: 'none'
border: 'none',
},
'& .editor-toolbar': { // полоски(разделители) иконок
'& .editor-toolbar': {
// полоски(разделители) иконок
background: 'transparent',
color: 'white',
border: 'none',
borderBottom: '1px solid #FFFFFF'
borderBottom: '1px solid #FFFFFF',
},
'& .editor-toolbar .fa': { // все иконки
'& .editor-toolbar .fa': {
// все иконки
color: 'white',
},
'& .CodeMirror': { // поле ввода
'& .CodeMirror': {
// поле ввода
backgroundColor: 'transparent',
color: 'white',
border: 'none'
border: 'none',
},
}}
>
@ -283,7 +380,7 @@ export const News = () => {
filter: 'brightness(1.05)',
transform: 'scale(1.02)',
},
transition: 'all 0.5s ease'
transition: 'all 0.5s ease',
}}
>
{creating ? 'Публикация...' : 'Опубликовать'}
@ -432,12 +529,10 @@ export const News = () => {
onClick={() => handleToggleExpand(item.id)}
sx={{
transform: isExpanded ? 'rotate(180deg)' : 'rotate(0deg)',
background:
'rgba(242,113,33,0.15)',
background: 'rgba(242,113,33,0.15)',
borderRadius: '1.4vw',
'&:hover': {
background:
'rgba(242,113,33,0.4)',
background: 'rgba(242,113,33,0.4)',
},
transition: 'all 0.25s ease',
}}
@ -505,7 +600,7 @@ export const News = () => {
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
p: ({ node, ...props }) => (
p: ({ node, children, ...props }) => (
<Typography
{...props}
sx={{
@ -513,13 +608,19 @@ export const News = () => {
fontSize: '1.5vw',
lineHeight: 1.6,
mb: 1,
whiteSpace: 'pre-line', // ← вот это
whiteSpace: 'pre-line',
textAlign: 'center', // вместо alignItems center
'&:last-of-type': { mb: 0 },
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
wordBreak: 'break-word',
overflowWrap: 'anywhere',
}}
/>
>
{React.Children.map(children, (child) =>
typeof child === 'string'
? renderWithPromoCodes(child)
: child,
)}
</Typography>
),
strong: ({ node, ...props }) => (
<Box
@ -691,6 +792,14 @@ export const News = () => {
</Paper>
);
})}
<CustomNotification
open={notifOpen}
message={notifMsg}
severity={notifSeverity}
position={notifPos}
onClose={() => setNotifOpen(false)}
autoHideDuration={2500}
/>
</Box>
</Box>
);