лучшено уведомление пользователя: добавлен модальный компонент ValidationModal для отображения уведомлений о необходимости выбрать стикеры или ввести название стикерпака
This commit is contained in:
parent
70933a6980
commit
f691b6e3de
@ -74,6 +74,10 @@
|
|||||||
gap: var(--spacing-small);
|
gap: var(--spacing-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: var(--spacing-small);
|
padding: var(--spacing-small);
|
||||||
|
|||||||
41
src/components/shared/ValidationModal.tsx
Normal file
41
src/components/shared/ValidationModal.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './NotificationModal.module.css';
|
||||||
|
|
||||||
|
interface ValidationModalProps {
|
||||||
|
isVisible: boolean;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
onContinue: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ValidationModal: React.FC<ValidationModalProps> = ({
|
||||||
|
isVisible,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
onContinue
|
||||||
|
}) => {
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<div className={styles.message}>{message}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.buttons}>
|
||||||
|
<div className={styles.spacer}></div> {/* Пустой элемент для выравнивания */}
|
||||||
|
<button
|
||||||
|
className={`${styles.button} ${styles.primaryButton}`}
|
||||||
|
onClick={onContinue}
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ValidationModal;
|
||||||
@ -5,6 +5,13 @@
|
|||||||
padding: calc(3rem + var(--spacing-small)) var(--spacing-medium) var(--spacing-large);
|
padding: calc(3rem + var(--spacing-small)) var(--spacing-medium) var(--spacing-large);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative; /* Добавляем для позиционирования фиксированной кнопки */
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh; /* Обеспечиваем, чтобы контент занимал как минимум всю высоту экрана */
|
||||||
|
padding-bottom: calc(4rem + 80px + var(--safe-area-inset-bottom)); /* Отступ для кнопки */
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -132,19 +139,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
/* Стили для контейнера кнопки */
|
||||||
display: flex;
|
.createButtonContainer {
|
||||||
justify-content: center;
|
position: fixed;
|
||||||
margin-top: var(--spacing-medium);
|
bottom: calc(6rem + var(--safe-area-inset-bottom)); /* Увеличенный отступ от навигации */
|
||||||
|
left: var(--spacing-medium);
|
||||||
|
right: var(--spacing-medium);
|
||||||
|
width: auto;
|
||||||
|
max-width: calc(28rem - 2 * var(--spacing-medium));
|
||||||
|
margin: 0 auto;
|
||||||
|
z-index: 90;
|
||||||
}
|
}
|
||||||
|
|
||||||
.createButton {
|
.createButton {
|
||||||
padding: var(--spacing-small) var(--spacing-large);
|
width: 100%;
|
||||||
|
padding: calc(var(--spacing-small) * 1.5) var(--spacing-large); /* Увеличенный вертикальный padding для толщины */
|
||||||
background-color: var(--color-primary);
|
background-color: var(--color-primary);
|
||||||
color: white;
|
color: white;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
font-size: 1.1rem; /* Увеличенный размер шрифта */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import apiService from '../services/api';
|
|||||||
import { GeneratedImage } from '../types/api';
|
import { GeneratedImage } from '../types/api';
|
||||||
import { getCurrentUserId } from '../constants/user';
|
import { getCurrentUserId } from '../constants/user';
|
||||||
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
|
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
|
||||||
|
import ValidationModal from '../components/shared/ValidationModal';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Транслитерирует кириллический текст в латиницу.
|
* Транслитерирует кириллический текст в латиницу.
|
||||||
@ -61,6 +62,11 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false);
|
const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false);
|
||||||
const [activeEmojiIndex, setActiveEmojiIndex] = useState<number | null>(null);
|
const [activeEmojiIndex, setActiveEmojiIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
// Состояния для модального окна валидации
|
||||||
|
const [validationTitle, setValidationTitle] = useState<string>('');
|
||||||
|
const [validationMessage, setValidationMessage] = useState<string>('');
|
||||||
|
const [isValidationModalVisible, setIsValidationModalVisible] = useState<boolean>(false);
|
||||||
|
|
||||||
// Функция для сворачивания клавиатуры
|
// Функция для сворачивания клавиатуры
|
||||||
const dismissKeyboard = () => {
|
const dismissKeyboard = () => {
|
||||||
if (titleInputRef.current) {
|
if (titleInputRef.current) {
|
||||||
@ -145,15 +151,24 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Обработчик закрытия модального окна валидации
|
||||||
|
const handleValidationModalClose = () => {
|
||||||
|
setIsValidationModalVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
// Обработчик создания стикерпака
|
// Обработчик создания стикерпака
|
||||||
const handleCreateStickerPack = async () => {
|
const handleCreateStickerPack = async () => {
|
||||||
if (!title.trim()) {
|
if (!title.trim()) {
|
||||||
setError('Введите название стикерпака');
|
setValidationTitle('Название не указано');
|
||||||
|
setValidationMessage('Пожалуйста, введите название для вашего стикерпака.');
|
||||||
|
setIsValidationModalVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedImages.length === 0) {
|
if (selectedImages.length === 0) {
|
||||||
setError('Выберите хотя бы одно изображение');
|
setValidationTitle('Стикеры не выбраны');
|
||||||
|
setValidationMessage('Пожалуйста, выберите хотя бы одно изображение для вашего стикерпака.');
|
||||||
|
setIsValidationModalVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +205,9 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
navigate('/packs');
|
navigate('/packs');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Ошибка при создании стикерпака:', err);
|
console.error('Ошибка при создании стикерпака:', err);
|
||||||
setError('Не удалось создать стикерпак');
|
setValidationTitle('Ошибка');
|
||||||
|
setValidationMessage('Не удалось создать стикерпак. Пожалуйста, попробуйте еще раз.');
|
||||||
|
setIsValidationModalVisible(true);
|
||||||
} finally {
|
} finally {
|
||||||
setCreating(false);
|
setCreating(false);
|
||||||
}
|
}
|
||||||
@ -208,80 +225,82 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
<h1 className={styles.title}>Создание стикерпака</h1>
|
<h1 className={styles.title}>Создание стикерпака</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.form}>
|
<div className={styles.content}>
|
||||||
<div className={styles.formGroup}>
|
<div className={styles.form}>
|
||||||
<label htmlFor="title" className={styles.label}>Название стикерпака</label>
|
<div className={styles.formGroup}>
|
||||||
<input
|
<label htmlFor="title" className={styles.label}>Название стикерпака</label>
|
||||||
id="title"
|
<input
|
||||||
type="text"
|
id="title"
|
||||||
className={styles.input}
|
type="text"
|
||||||
value={title}
|
className={styles.input}
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
value={title}
|
||||||
placeholder="Введите название стикерпака"
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
ref={titleInputRef}
|
placeholder="Введите название стикерпака"
|
||||||
onKeyDown={handleTitleKeyDown}
|
ref={titleInputRef}
|
||||||
enterKeyHint="done"
|
onKeyDown={handleTitleKeyDown}
|
||||||
/>
|
enterKeyHint="done"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formGroup}>
|
||||||
|
<label className={styles.label}>Выберите изображения для стикеров</label>
|
||||||
|
|
||||||
|
{loading && <p className={styles.loading}>Загрузка изображений...</p>}
|
||||||
|
|
||||||
|
{!loading && availableImages.length === 0 && (
|
||||||
|
<p className={styles.noImages}>
|
||||||
|
У вас пока нет сгенерированных изображений.
|
||||||
|
Сначала создайте изображения в разделе "Создать стикер".
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!loading && availableImages.length > 0 && (
|
||||||
|
<div className={styles.imagesGrid}>
|
||||||
|
{availableImages.map((image, index) => {
|
||||||
|
const isSelected = selectedImages.some(img => img.id === image.id);
|
||||||
|
const selectedIndex = selectedImages.findIndex(img => img.id === image.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`}
|
||||||
|
onClick={() => handleImageSelect(image)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={image.url || ''}
|
||||||
|
alt={`Изображение ${index + 1}`}
|
||||||
|
className={styles.image}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{isSelected && (
|
||||||
|
<div className={styles.emojiSelector}>
|
||||||
|
<button
|
||||||
|
className={styles.emojiButton}
|
||||||
|
onClick={(e) => openEmojiPicker(selectedIndex, e)}
|
||||||
|
>
|
||||||
|
{emojis[selectedIndex] || '😊'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <p className={styles.error}>{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.formGroup}>
|
<div className={styles.createButtonContainer}>
|
||||||
<label className={styles.label}>Выберите изображения для стикеров</label>
|
<button
|
||||||
|
className={styles.createButton}
|
||||||
{loading && <p className={styles.loading}>Загрузка изображений...</p>}
|
onClick={handleCreateStickerPack}
|
||||||
|
disabled={creating}
|
||||||
{!loading && availableImages.length === 0 && (
|
>
|
||||||
<p className={styles.noImages}>
|
{creating ? 'Создание...' : 'Создать стикерпак'}
|
||||||
У вас пока нет сгенерированных изображений.
|
</button>
|
||||||
Сначала создайте изображения в разделе "Создать стикер".
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!loading && availableImages.length > 0 && (
|
|
||||||
<div className={styles.imagesGrid}>
|
|
||||||
{availableImages.map((image, index) => {
|
|
||||||
const isSelected = selectedImages.some(img => img.id === image.id);
|
|
||||||
const selectedIndex = selectedImages.findIndex(img => img.id === image.id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`}
|
|
||||||
onClick={() => handleImageSelect(image)}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={image.url || ''}
|
|
||||||
alt={`Изображение ${index + 1}`}
|
|
||||||
className={styles.image}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isSelected && (
|
|
||||||
<div className={styles.emojiSelector}>
|
|
||||||
<button
|
|
||||||
className={styles.emojiButton}
|
|
||||||
onClick={(e) => openEmojiPicker(selectedIndex, e)}
|
|
||||||
>
|
|
||||||
{emojis[selectedIndex] || '😊'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error && <p className={styles.error}>{error}</p>}
|
|
||||||
|
|
||||||
<div className={styles.actions}>
|
|
||||||
<button
|
|
||||||
className={styles.createButton}
|
|
||||||
onClick={handleCreateStickerPack}
|
|
||||||
disabled={creating || selectedImages.length === 0}
|
|
||||||
>
|
|
||||||
{creating ? 'Создание...' : 'Создать стикерпак'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Модальное окно выбора эмодзи */}
|
{/* Модальное окно выбора эмодзи */}
|
||||||
@ -291,6 +310,14 @@ const CreateStickerPack: React.FC = () => {
|
|||||||
onSelect={handleSelectEmoji}
|
onSelect={handleSelectEmoji}
|
||||||
initialEmoji={activeEmojiIndex !== null ? (emojis[activeEmojiIndex] || '😊') : '😊'}
|
initialEmoji={activeEmojiIndex !== null ? (emojis[activeEmojiIndex] || '😊') : '😊'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Модальное окно валидации */}
|
||||||
|
<ValidationModal
|
||||||
|
isVisible={isValidationModalVisible}
|
||||||
|
title={validationTitle}
|
||||||
|
message={validationMessage}
|
||||||
|
onContinue={handleValidationModalClose}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user