PopularProducts, SkinTypeQuiz
This commit is contained in:
10
src/App.js
10
src/App.js
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Header from './components/Header/Header';
|
||||
import SkinTypeQuiz from './components/SkinTypeQuiz/SkinTypeQuiz';
|
||||
import PopularProducts from './components/PopularProducts/PopularProducts';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
@@ -8,8 +9,13 @@ function App() {
|
||||
<div className="App">
|
||||
<Header />
|
||||
<main>
|
||||
<h1>Добро пожаловать в SkinCare Advisor!</h1>
|
||||
<SkinTypeQuiz/>
|
||||
<h1 style={{ textAlign: 'center', marginTop: '20px' }}>
|
||||
Добро пожаловать в SkinCare Advisor!
|
||||
</h1>
|
||||
<section style={{ maxWidth: '700px', margin: '40px auto', padding: '0 20px' }}>
|
||||
<SkinTypeQuiz />
|
||||
</section>
|
||||
<PopularProducts />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
77
src/components/PopularProducts/PopularProducts.css
Normal file
77
src/components/PopularProducts/PopularProducts.css
Normal 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; /* Показываем кнопку при наведении */
|
||||
}
|
||||
|
||||
64
src/components/PopularProducts/PopularProducts.js
Normal file
64
src/components/PopularProducts/PopularProducts.js
Normal 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;
|
||||
BIN
src/components/PopularProducts/img/product1.jpg
Normal file
BIN
src/components/PopularProducts/img/product1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 176 KiB |
BIN
src/components/PopularProducts/img/product2.jpg
Normal file
BIN
src/components/PopularProducts/img/product2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
src/components/PopularProducts/img/product3.jpg
Normal file
BIN
src/components/PopularProducts/img/product3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
src/components/PopularProducts/img/product4.jpg
Normal file
BIN
src/components/PopularProducts/img/product4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
@@ -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;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
function Result({ recommendations }) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Результаты опроса</h2>
|
||||
<p>{recommendations}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Result;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 Question from './Question.js';
|
||||
import Result from './Result.js';
|
||||
|
||||
const quiz = [
|
||||
{
|
||||
id: 1,
|
||||
text: "Ваш пол?",
|
||||
type: "single",
|
||||
options: ["Мужской", "Женcкий",]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Сколько вам лет?",
|
||||
type: "single",
|
||||
options: ["Менее 18", "18-25", "26-35", "36-45", , "46-55", "Более 55"]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Опишите кожу сразу после умывания:",
|
||||
type: "single",
|
||||
options: ["Сухая и тянущаяся", "Нормальная, без ощущения сухости или жирности", "Жирная, блестящая", " Комбинированная (нормальная на щеках, жирная на Т-зоне)"]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "Как часто вы испытываете раздражение или покраснение кожи?",
|
||||
type: "single",
|
||||
options: ["Часто", "Редко", "Практически никогда"]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Как вы оцениваете чувствительность вашей кожи?",
|
||||
type: "single",
|
||||
options: ["Очень чувствительная", "Средняя чувствительность", "Мало чувствительная"]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
text: "Какие проблемы с кожей вас беспокоят? (выберите все, что применимо)",
|
||||
type: "multiple",
|
||||
options: ["Акне", "Черные точки", "Пигментация", "Морщины", "Другое (укажите)"]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
text: "Какие косметические средства вы обычно используете? (выберите все, что применимо)",
|
||||
type: "multiple",
|
||||
options: ["Крем для лица", "Сыворотка", "Маска", "Тональный крем", "Другое (укажите)"]
|
||||
const questionsMock = [
|
||||
{
|
||||
id: 1,
|
||||
text: "Ваш пол?",
|
||||
type: "single",
|
||||
options: ["Мужской", "Женcкий"]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Сколько вам лет?",
|
||||
type: "single",
|
||||
options: ["Менее 18", "18-25", "26-35", "36-45", "46-55", "Более 55"]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "Опишите кожу сразу после умывания:",
|
||||
type: "single",
|
||||
options: ["Сухая и тянущаяся", "Нормальная, без ощущения сухости или жирности", "Жирная, блестящая", "Комбинированная (нормальная на щеках, жирная на Т-зоне)"]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
text: "Как часто вы испытываете раздражение или покраснение кожи?",
|
||||
type: "single",
|
||||
options: ["Часто", "Редко", "Практически никогда"]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
text: "Как вы оцениваете чувствительность вашей кожи?",
|
||||
type: "single",
|
||||
options: ["Очень чувствительная", "Средняя чувствительность", "Мало чувствительная"]
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
text: "Какие проблемы с кожей вас беспокоят? (выберите все, что применимо)",
|
||||
type: "multiple",
|
||||
options: ["Акне", "Черные точки", "Пигментация", "Морщины", "Другое (укажите)"]
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
text: "Какие косметические средства вы обычно используете? (выберите все, что применимо)",
|
||||
type: "multiple",
|
||||
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;
|
||||
Reference in New Issue
Block a user