PopularProducts, SkinTypeQuiz
This commit is contained in:
10
src/App.js
10
src/App.js
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
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,26 +1,26 @@
|
|||||||
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,
|
||||||
@@ -46,8 +46,59 @@ const quiz = [
|
|||||||
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;
|
||||||
Reference in New Issue
Block a user