PopularProducts, SkinTypeQuiz

This commit is contained in:
Evdokia
2025-04-23 14:14:35 +03:00
parent fa7f3b49f9
commit 5ce92c4b81
11 changed files with 415 additions and 53 deletions

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import Header from './components/Header/Header'; import Header from './components/Header/Header';
import SkinTypeQuiz from './components/SkinTypeQuiz/SkinTypeQuiz'; import SkinTypeQuiz from './components/SkinTypeQuiz/SkinTypeQuiz';
import PopularProducts from './components/PopularProducts/PopularProducts';
import './App.css'; import './App.css';
function App() { function App() {
@@ -8,8 +9,13 @@ function App() {
<div className="App"> <div className="App">
<Header /> <Header />
<main> <main>
<h1>Добро пожаловать в SkinCare Advisor!</h1> <h1 style={{ textAlign: 'center', marginTop: '20px' }}>
<SkinTypeQuiz/> Добро пожаловать в SkinCare Advisor!
</h1>
<section style={{ maxWidth: '700px', margin: '40px auto', padding: '0 20px' }}>
<SkinTypeQuiz />
</section>
<PopularProducts />
</main> </main>
</div> </div>
); );

View File

@@ -0,0 +1,77 @@
.popular-products {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
}
.popular-products h2 {
text-align: center;
margin-bottom: 40px;
font-size: 28px;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
gap: 30px;
}
.product-card {
border: 1px solid #eee;
border-radius: 12px;
overflow: hidden;
transition: transform 0.3s;
}
.product-card:hover {
transform: translateY(-5px);
}
.product-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.product-card h3 {
padding: 15px 15px 0;
margin: 0;
font-size: 18px;
}
.product-card p {
padding: 10px 15px;
color: #666;
font-size: 14px;
margin: 0;
}
.product-footer {
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.price {
font-weight: bold;
color: #333;
}
.details-btn {
background: #f8f9fa;
border: 1px solid #ddd;
padding: 8px 15px;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
opacity: 0; /* Скрываем кнопку по умолчанию */
}
.product-card:hover .details-btn {
background: #6C63FF;
color: white;
border-color: #6C63FF;
opacity: 1; /* Показываем кнопку при наведении */
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import './PopularProducts.css';
import product1 from './img/product1.jpg';
import product2 from './img/product2.jpg';
import product3 from './img/product3.jpg';
import product4 from './img/product4.jpg';
const products = [
{
id: 1,
name: "Гиалуроновый крем",
image: product1,
description: "Интенсивное увлажнение на 24 часа",
price: "1 490 ₽"
},
{
id: 2,
name: "Сыворотка с витамином С",
image: product2,
description: "Освежает и выравнивает тон кожи",
price: "2 350 ₽"
},
{
id: 3,
name: "Мицеллярная вода",
image: product3,
description: "Мгновенно удаляет макияж",
price: "890 ₽"
},
{
id: 4,
name: "Ночной крем",
image: product4,
description: "Восстановление во время сна",
price: "1 950 ₽"
}
];
function PopularProducts() {
return (
<section className="popular-products">
<h2>Популярные косметические средства</h2>
<div className="products-grid">
{products.map(product => (
<div key={product.id} className="product-card">
<img
src={product.image}
alt={product.name}
className="product-image"
/>
<h3>{product.name}</h3>
<p>{product.description}</p>
<div className="product-footer">
<span className="price">{product.price}</span>
<button className="details-btn">Подробнее</button>
</div>
</div>
))}
</div>
</section>
);
}
export default PopularProducts;

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,118 @@
import React, { useState, useEffect } from 'react';
function Question({ question, answer, onAnswer, isLast, onNext }) {
const [selected, setSelected] = useState(() => {
return question.type === 'multiple'
? Array.isArray(answer) ? [...answer] : []
: answer || '';
});
const [otherText, setOtherText] = useState('');
// Инициализация otherText
useEffect(() => {
if (question.type === 'multiple' && Array.isArray(answer)) {
const otherItem = answer.find(item => item?.other);
setOtherText(otherItem?.otherText || '');
}
}, [answer, question.type]);
// Синхронизация с пропсом answer
useEffect(() => {
if (question.type === 'multiple') {
setSelected(Array.isArray(answer) ? answer : []);
} else {
setSelected(typeof answer === 'string' ? answer : '');
}
}, [answer, question.type]);
const handleChange = (option, isOther = false) => {
if (question.type === 'single') {
setSelected(option);
onAnswer(question.id, option);
return;
}
let newSelected = Array.isArray(selected) ? [...selected] : [];
if (isOther) {
const existingOther = newSelected.findIndex(item => item?.other);
if (existingOther >= 0) {
newSelected.splice(existingOther, 1);
setOtherText('');
} else {
newSelected.push({ other: true, otherText: '' });
}
} else {
const index = newSelected.findIndex(item => item === option);
if (index >= 0) {
newSelected.splice(index, 1);
} else {
newSelected.push(option);
}
}
setSelected(newSelected);
onAnswer(question.id, newSelected);
};
const handleOtherTextChange = (e) => {
const text = e.target.value;
setOtherText(text);
const newSelected = (Array.isArray(selected) ? selected : []).map(item => {
return item?.other ? { ...item, otherText: text } : item;
});
setSelected(newSelected);
onAnswer(question.id, newSelected);
};
const handleSubmit = (e) => {
e.preventDefault();
onNext();
};
const safeSelected = question.type === 'multiple'
? (Array.isArray(selected) ? selected : [])
: selected;
return (
<form onSubmit={handleSubmit}>
<h3>{question.text}</h3>
<div>
{question.options.map(option => {
const isOther = option.toLowerCase().includes('другое');
const isChecked = question.type === 'multiple'
? isOther
? safeSelected.some(item => item?.other)
: safeSelected.includes(option)
: safeSelected === option;
return (
<div key={option} style={{ marginBottom: '10px' }}>
<label>
<input
type={question.type === 'single' ? 'radio' : 'checkbox'}
checked={isChecked}
onChange={() => handleChange(option, isOther)}
/>
{option}
</label>
{isOther && safeSelected.some(item => item?.other) && (
<input
type="text"
placeholder="Укажите..."
value={otherText}
onChange={handleOtherTextChange}
style={{ marginLeft: '10px' }}
/>
)}
</div>
);
})}
</div>
<button type="submit">{isLast ? 'Завершить' : 'Далее'}</button>
</form>
);
}
export default Question;

View File

@@ -0,0 +1,12 @@
import React from 'react';
function Result({ recommendations }) {
return (
<div>
<h2>Результаты опроса</h2>
<p>{recommendations}</p>
</div>
);
}
export default Result;

View File

@@ -0,0 +1,34 @@
form {
max-width: 600px;
margin: 20px auto;
padding: 20px;
background-color: #fafafa;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 0 8px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
}
h3 {
margin-bottom: 16px;
}
label {
cursor: pointer;
}
button {
margin-top: 20px;
padding: 10px 24px;
background-color: #007bff;
border: none;
color: white;
font-size: 16px;
border-radius: 6px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}

View File

@@ -1,53 +1,104 @@
import React from 'react'; import React, { useState } from 'react';
import Question from './Question';
import Result from './Result';
import './SkinTypeQuiz.css'; import './SkinTypeQuiz.css';
import Question from './Question.js';
import Result from './Result.js';
const quiz = [ const questionsMock = [
{ {
id: 1, id: 1,
text: "Ваш пол?", text: "Ваш пол?",
type: "single", type: "single",
options: ["Мужской", енcкий",] options: ["Мужской", енcкий"]
}, },
{ {
id: 2, id: 2,
text: "Сколько вам лет?", text: "Сколько вам лет?",
type: "single", type: "single",
options: ["Менее 18", "18-25", "26-35", "36-45", , "46-55", "Более 55"] options: ["Менее 18", "18-25", "26-35", "36-45", "46-55", "Более 55"]
}, },
{ {
id: 3, id: 3,
text: "Опишите кожу сразу после умывания:", text: "Опишите кожу сразу после умывания:",
type: "single", type: "single",
options: ["Сухая и тянущаяся", "Нормальная, без ощущения сухости или жирности", "Жирная, блестящая", " Комбинированная (нормальная на щеках, жирная на Т-зоне)"] options: ["Сухая и тянущаяся", "Нормальная, без ощущения сухости или жирности", "Жирная, блестящая", "Комбинированная (нормальная на щеках, жирная на Т-зоне)"]
}, },
{ {
id: 4, id: 4,
text: "Как часто вы испытываете раздражение или покраснение кожи?", text: "Как часто вы испытываете раздражение или покраснение кожи?",
type: "single", type: "single",
options: ["Часто", "Редко", "Практически никогда"] options: ["Часто", "Редко", "Практически никогда"]
}, },
{ {
id: 5, id: 5,
text: "Как вы оцениваете чувствительность вашей кожи?", text: "Как вы оцениваете чувствительность вашей кожи?",
type: "single", type: "single",
options: ["Очень чувствительная", "Средняя чувствительность", "Мало чувствительная"] options: ["Очень чувствительная", "Средняя чувствительность", "Мало чувствительная"]
}, },
{ {
id: 6, id: 6,
text: "Какие проблемы с кожей вас беспокоят? (выберите все, что применимо)", text: "Какие проблемы с кожей вас беспокоят? (выберите все, что применимо)",
type: "multiple", type: "multiple",
options: ["Акне", "Черные точки", "Пигментация", "Морщины", "Другое (укажите)"] options: ["Акне", "Черные точки", "Пигментация", "Морщины", "Другое (укажите)"]
}, },
{ {
id: 7, id: 7,
text: "Какие косметические средства вы обычно используете? (выберите все, что применимо)", text: "Какие косметические средства вы обычно используете? (выберите все, что применимо)",
type: "multiple", type: "multiple",
options: ["Крем для лица", "Сыворотка", "Маска", "Тональный крем", "Другое (укажите)"] options: ["Крем для лица", "Сыворотка", "Маска", "Тональный крем", "Другое (укажите)"]
}
];
function SkinTypeQuiz() {
const [currentIndex, setCurrentIndex] = useState(0);
const [answers, setAnswers] = useState({});
const [showResult, setShowResult] = useState(false);
const [recommendations, setRecommendations] = useState('');
const questions = questionsMock;
const handleAnswer = (questionId, answer) => {
const currentQuestion = questions.find(q => q.id === questionId);
const safeAnswer = currentQuestion.type === 'multiple'
? Array.isArray(answer) ? answer : []
: answer;
setAnswers(prev => ({ ...prev, [questionId]: safeAnswer }));
};
const handleNext = () => {
if (currentIndex + 1 < questions.length) {
setCurrentIndex(currentIndex + 1);
} else {
const rec = calculateRecommendations(answers);
setRecommendations(rec);
setShowResult(true);
} }
] };
if (showResult) {
return <Result recommendations={recommendations} />;
}
return (
<Question
question={questions[currentIndex]}
answer={answers[questions[currentIndex].id]}
onAnswer={handleAnswer}
isLast={currentIndex === questions.length - 1}
onNext={handleNext}
/>
);
}
function calculateRecommendations(answers) {
const question3Answer = answers[3]; // ID=3 — вопрос про тип кожи
if (question3Answer === 'Сухая и тянущаяся') {
return 'Рекомендуется использовать увлажняющие средства и средства для чувствительной кожи.';
}
if (question3Answer === 'Жирная, блестящая') {
return 'Рекомендуется использовать средства для жирной кожи и против акне.';
}
return 'Рекомендуется использовать базовый уход за кожей.';
}
export default SkinTypeQuiz; export default SkinTypeQuiz;