add promo to News
This commit is contained in:
@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user