лучшено уведомление пользователя: добавлен модальный компонент ValidationModal для отображения уведомлений о необходимости выбрать стикеры или ввести название стикерпака

This commit is contained in:
kazachilo 2025-03-17 16:25:57 +03:00
parent 70933a6980
commit f691b6e3de
4 changed files with 166 additions and 79 deletions

View File

@ -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);

View 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;

View File

@ -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;
} }

View File

@ -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,82 +225,84 @@ 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>
<div className={styles.formGroup}> <div className={styles.formGroup}>
<label className={styles.label}>Выберите изображения для стикеров</label> <label className={styles.label}>Выберите изображения для стикеров</label>
{loading && <p className={styles.loading}>Загрузка изображений...</p>} {loading && <p className={styles.loading}>Загрузка изображений...</p>}
{!loading && availableImages.length === 0 && ( {!loading && availableImages.length === 0 && (
<p className={styles.noImages}> <p className={styles.noImages}>
У вас пока нет сгенерированных изображений. У вас пока нет сгенерированных изображений.
Сначала создайте изображения в разделе "Создать стикер". Сначала создайте изображения в разделе "Создать стикер".
</p> </p>
)} )}
{!loading && availableImages.length > 0 && ( {!loading && availableImages.length > 0 && (
<div className={styles.imagesGrid}> <div className={styles.imagesGrid}>
{availableImages.map((image, index) => { {availableImages.map((image, index) => {
const isSelected = selectedImages.some(img => img.id === image.id); const isSelected = selectedImages.some(img => img.id === image.id);
const selectedIndex = selectedImages.findIndex(img => img.id === image.id); const selectedIndex = selectedImages.findIndex(img => img.id === image.id);
return ( return (
<div <div
key={index} key={index}
className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`} className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`}
onClick={() => handleImageSelect(image)} onClick={() => handleImageSelect(image)}
> >
<img <img
src={image.url || ''} src={image.url || ''}
alt={`Изображение ${index + 1}`} alt={`Изображение ${index + 1}`}
className={styles.image} className={styles.image}
/> />
{isSelected && ( {isSelected && (
<div className={styles.emojiSelector}> <div className={styles.emojiSelector}>
<button <button
className={styles.emojiButton} className={styles.emojiButton}
onClick={(e) => openEmojiPicker(selectedIndex, e)} onClick={(e) => openEmojiPicker(selectedIndex, e)}
> >
{emojis[selectedIndex] || '😊'} {emojis[selectedIndex] || '😊'}
</button> </button>
</div> </div>
)} )}
</div> </div>
); );
})} })}
</div> </div>
)} )}
</div> </div>
{error && <p className={styles.error}>{error}</p>} {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> </div>
<div className={styles.createButtonContainer}>
<button
className={styles.createButton}
onClick={handleCreateStickerPack}
disabled={creating}
>
{creating ? 'Создание...' : 'Создать стикерпак'}
</button>
</div>
{/* Модальное окно выбора эмодзи */} {/* Модальное окно выбора эмодзи */}
<EmojiPickerModal <EmojiPickerModal
isVisible={isEmojiPickerVisible && activeEmojiIndex !== null} isVisible={isEmojiPickerVisible && activeEmojiIndex !== null}
@ -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>
); );
}; };