import React, { useState, useEffect, useRef } from 'react';
import { initializeApp } from "firebase/app";
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from "firebase/auth";
import { getFirestore, doc, setDoc, onSnapshot } from "firebase/firestore";
// Firebase configuration: Use environment variables or placeholders.
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_AUTH_DOMAIN",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_STORAGE_BUCKET",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
};
// Initialize Firebase app, auth, and Firestore
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-emotion-workbook';
// Helper: Reusable Modal Component
const Modal = ({ show, onClose, title, children, size = 'lg' }) => {
if (!show) return null;
const sizeClasses = {
sm: 'max-w-sm',
lg: 'max-w-lg',
xl: 'max-w-xl'
};
return (
);
};
// Helper: Reusable Accordion Component
const Accordion = ({ title, children }) => {
const [isOpen, setIsOpen] = useState(false);
return (
setIsOpen(!isOpen)}
className="w-full flex justify-between items-center p-5 bg-white hover:bg-orange-50 transition-colors"
>
{title}
{isOpen ? '−' : '+'}
{isOpen &&
{children}
}
);
};
// Drawing Canvas Component
const DrawingCanvas = ({ onSave, initialDrawing }) => {
const canvasRef = useRef(null);
const [tool, setTool] = useState('pen'); // 'pen', 'marker', 'spray', 'eraser', 'sticker'
const [color, setColor] = useState('#4A4A4A');
const [brushSize, setBrushSize] = useState(10);
const [selectedSticker, setSelectedSticker] = useState(null);
const history = useRef([null]);
const historyStep = useRef(0);
const isDrawing = useRef(false);
const palette = ['#4A4A4A', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#4A90E2', '#50E3C2', '#BD10E0', '#9013FE', '#E94E77'];
const stickers = {
happy: '😊', sad: '😢', angry: '😠', love: '❤️', star: '⭐', spiral: '🌀', lightning: '⚡️', fire: '🔥', water: '💧', broken: '💔'
};
// Save drawing history for undo/redo
const saveHistory = () => {
if (historyStep.current < history.current.length - 1) {
history.current = history.current.slice(0, historyStep.current + 1);
}
const canvas = canvasRef.current;
if(canvas) {
history.current.push(canvas.toDataURL());
historyStep.current++;
}
};
// Redraw canvas content
const redrawCanvas = (dataUrl, callback) => {
const canvas = canvasRef.current;
if(!canvas) return;
const context = canvas.getContext('2d');
if (!dataUrl) {
context.clearRect(0, 0, canvas.width, canvas.height);
if (callback) callback();
return;
}
const image = new Image();
image.onload = () => {
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(image, 0, 0, canvas.width * 0.5, canvas.height * 0.5);
if(callback) callback();
};
image.src = dataUrl;
};
// Undo action
const handleUndo = () => {
if (historyStep.current > 1) {
historyStep.current--;
const previousState = history.current[historyStep.current - 1];
redrawCanvas(previousState);
onSave(previousState);
}
};
// Redo action
const handleRedo = () => {
if (historyStep.current < history.current.length) {
const nextState = history.current[historyStep.current];
redrawCanvas(nextState);
onSave(nextState);
historyStep.current++;
}
};
// Initialize canvas and load saved drawing
useEffect(() => {
const canvas = canvasRef.current;
canvas.width = 500 * 2;
canvas.height = 500 * 2;
canvas.style.width = `500px`;
canvas.style.height = `500px`;
const context = canvas.getContext("2d");
context.scale(2, 2);
redrawCanvas(initialDrawing, () => {
if (canvasRef.current) {
history.current = [initialDrawing || canvasRef.current.toDataURL()];
historyStep.current = 1;
}
});
}, [initialDrawing]);
// Get coordinates for mouse/touch events
const getCoords = (e) => {
const rect = canvasRef.current.getBoundingClientRect();
const event = e.touches && e.touches.length > 0 ? e.touches[0] : e;
return {
offsetX: event.clientX - rect.left,
offsetY: event.clientY - rect.top,
};
}
// Place sticker on canvas
const placeSticker = (e) => {
if (!selectedSticker) return;
const { offsetX, offsetY } = getCoords(e);
const context = canvasRef.current.getContext('2d');
context.font = `${brushSize * 2}px sans-serif`;
context.textAlign = 'center';
context.textBaseline = 'middle';
context.globalCompositeOperation = 'source-over';
context.fillStyle = color;
context.fillText(stickers[selectedSticker], offsetX, offsetY);
saveHistory();
onSave(canvasRef.current.toDataURL("image/png"));
setSelectedSticker(null);
setTool('pen');
};
// Start drawing
const startDrawing = (e) => {
e.preventDefault();
if (tool === 'sticker') {
placeSticker(e);
return;
}
const { offsetX, offsetY } = getCoords(e);
const context = canvasRef.current.getContext('2d');
isDrawing.current = true;
context.beginPath();
context.moveTo(offsetX, offsetY);
context.lineWidth = brushSize;
context.strokeStyle = color;
context.globalAlpha = tool === 'marker' ? 0.3 : 1.0;
context.lineCap = tool === 'marker' ? 'square' : 'round';
context.lineJoin = tool === 'marker' ? 'miter' : 'round';
context.globalCompositeOperation = tool === 'eraser' ? 'destination-out' : 'source-over';
};
// Continue drawing
const draw = (e) => {
if (!isDrawing.current) return;
e.preventDefault();
const { offsetX, offsetY } = getCoords(e);
const context = canvasRef.current.getContext('2d');
if (tool === 'spray') {
for (let i = 0; i < 15; i++) {
const angle = Math.random() * 2 * Math.PI;
const radius = Math.random() * brushSize;
const sprayX = offsetX + Math.cos(angle) * radius;
const sprayY = offsetY + Math.sin(angle) * radius;
context.fillStyle = color;
context.fillRect(sprayX, sprayY, 1, 1);
}
} else {
context.lineTo(offsetX, offsetY);
context.stroke();
}
};
// Finish drawing
const finishDrawing = () => {
if (!isDrawing.current) return;
isDrawing.current = false;
const context = canvasRef.current.getContext('2d');
context.closePath();
context.globalAlpha = 1.0;
saveHistory();
const dataUrl = canvasRef.current.toDataURL("image/png");
onSave(dataUrl);
};
const ToolButton = ({ onClick, currentTool, toolName, title, children }) => (
{children}
);
return (
{palette.map(c => (
setColor(c)} className={`w-8 h-8 rounded-full transition-transform transform hover:scale-110 ${color === c ? 'ring-2 ring-offset-2 ring-orange-500' : ''}`} style={{backgroundColor: c}}>
))}
setTool('pen')} currentTool={tool} toolName='pen' title="펜">
setTool('marker')} currentTool={tool} toolName='marker' title="마커">
setTool('spray')} currentTool={tool} toolName='spray' title="스프레이">
setTool('eraser')} currentTool={tool} toolName='eraser' title="지우개">
{Object.entries(stickers).map(([name, emoji]) => (
{ setTool('sticker'); setSelectedSticker(name); }} className={`text-2xl rounded-full p-2 transition-transform transform hover:scale-125 ${selectedSticker === name ? 'bg-orange-200' : ''}`}>{emoji}
))}
크기 조절
setBrushSize(e.target.value)} className="w-full accent-orange-500"/>
);
};
// Step 1 Component
const Step1 = ({ data, onUpdate, onNext }) => {
const handleUpdate = (field, value) => onUpdate('step1', { ...data.step1, [field]: value });
const isComplete = data.step1?.topic && data.step1?.drawingA && data.step1?.drawingAName;
const [showExampleModal, setShowExampleModal] = useState(false);
const [selectedExample, setSelectedExample] = useState(null);
// Updated with user-provided image links
const examples = [
{ title: '조화로운 마음', imgSrc: 'https://postfiles.pstatic.net/MjAyNTA3MDdfMyAg/MDAxNzUxODcyNjM2Mjk2.VchHBlmqLtskbA5fX2xqCsKqloaTPwVpML65qF55tGQg.MDv5Ll0Jo6hrLiRj3n0O2N2Ju_Geq3giXVnbsHz1stog.JPEG/unnamed_(4).jpg?type=w773' },
{ title: '기쁨', imgSrc: 'https://postfiles.pstatic.net/MjAyNTA3MDdfMjQ2/MDAxNzUxODcyNjM2Mjk1.EoNEdgfSLJTG5IQ1yOkBZF9ZBOdivORb1RRD11Zkm3Qg._QSTTe93ZLZSyy_q5jmYCJXShufYsjwueHL6COzOn4kg.JPEG/unnamed_(3).jpg?type=w773' },
{ title: '갑자기 끓어오르는 분노', imgSrc: 'https://postfiles.pstatic.net/MjAyNTA3MDdfMjc4/MDAxNzUxODcyNjM2Mjk3._zbEJoPQIhmHp3SgvVC8CN69jzo0jVFE5_n3Y9M8N3Ug.Ub6KQTPWt3_a7SDuyWzo6cruGGenw07J_No2cm43IZog.JPEG/unnamed_(2).jpg?type=w773' },
{ title: '나를 사랑하기', imgSrc: 'https://postfiles.pstatic.net/MjAyNTA3MDdfMjc0/MDAxNzUxODcyNjM2Mjk2.ylScAm5rxttwGbgP2QX5CTDfJqVcCNhxVYuUEvl_o6gg.WE7g0M64DQmNPwc_R15IKHigQBS94akqPpZmwDh2HFEg.JPEG/unnamed_(1).jpg?type=w773' },
];
const openExample = (ex) => {
setSelectedExample(ex);
setShowExampleModal(true);
};
return (
1단계: 감정 탐색하기
다루고 싶은 감정을 정하고, 몸으로 느껴 그려봅니다.
1-1. 감정 주제 정하기
최근 나의 관계에 영향을 미친 감정
해결되지 않고 마음에 남아 있는 감정
특정 상황에서 계속 반복되는 감정
이해하지 못해서 혼란스러운 감정
너무 압도적이거나 감당하기 어려운 감정은 피하세요.
과거 기억은 생생하지만 더 이상 감정이 동반되지 않는 경우는 적합하지 않습니다.
1-2. 감정 스케치 A: 나의 감정 자화상
주제를 정한 감정을 떠올렸을 때 몸에서 느껴지는 감각을 자유롭게 그려보세요.
다른 사람들의 감정 스케치 예시
{examples.map((ex, i) => (
openExample(ex)}>
))}
handleUpdate('drawingA', drawing)} initialDrawing={data.step1?.drawingA} />
이 감정에 이름을 붙여준다면?
handleUpdate('drawingAName', e.target.value)}
className="w-full max-w-sm mx-auto block p-4 border-2 border-gray-200 bg-gray-50 rounded-2xl text-center"
/>
setShowExampleModal(false)} title={`감정 스케치 예시: ${selectedExample?.title || ''}`} size="xl">
{selectedExample && (
)}
{isComplete ? '다음 단계로' : '모든 항목을 채워주세요'}
);
};
// Step 2 Component
const Step2 = ({ data, onUpdate, onNext, onPrev }) => {
const [showModal, setShowModal] = useState(false);
const [currentMemory, setCurrentMemory] = useState({ id: null, text: '', age: '', connections: '', intensity: 5 });
const handleAddMemory = () => {
setCurrentMemory({ id: null, text: '', age: '', connections: '', intensity: 5 });
setShowModal(true);
};
const handleEditMemory = (memory) => {
setCurrentMemory(memory);
setShowModal(true);
};
const handleSaveMemory = () => {
const memories = data.step2?.memories || [];
if (currentMemory.id) {
const updatedMemories = memories.map(mem => mem.id === currentMemory.id ? currentMemory : mem);
onUpdate('step2', { ...data.step2, memories: updatedMemories });
} else {
const newMemory = { ...currentMemory, id: Date.now() };
onUpdate('step2', { ...data.step2, memories: [...memories, newMemory] });
}
setShowModal(false);
};
const handleDeleteMemory = (id) => {
const memories = data.step2?.memories || [];
const updatedMemories = memories.filter(mem => mem.id !== id);
onUpdate('step2', { ...data.step2, memories: updatedMemories });
}
const memories = data.step2?.memories || [];
const isComplete = memories.length > 0;
return (
2단계: 감정 히스토리맵
나의 감정의 역사를 타임라인에 기록해봅니다.
{memories.length === 0 ? (
아직 기록된 기억이 없습니다.
'기억 추가하기' 버튼을 눌러 시작해보세요.
) : (
{memories.map(memory => (
{memory.text}
감정의 나이: {memory.age}살
연결고리: {memory.connections}
handleEditMemory(memory)} className="text-blue-500 hover:text-blue-700 font-bold">수정
handleDeleteMemory(memory.id)} className="text-red-500 hover:text-red-700">
))}
)}
setShowModal(false)} title={currentMemory.id ? "기억 수정하기" : "새로운 기억 추가"}>
이전 단계
{isComplete ? '다음 단계로' : '기억을 하나 이상 추가해주세요'}
);
};
// Step 3 Component
const Step3 = ({ data, onUpdate, onNext, onPrev }) => {
const handleUpdate = (field, value) => onUpdate('step3', { ...data.step3, [field]: value });
const isComplete = data.step3?.writing;
return (
3단계: 감정 글쓰기
표현되지 못하고 쌓여 있는 마음들을 자유롭게 풀어내 봅니다.
글쓰기 가이드
그 감정과 관련해서 떠오르는 생각이나 이야기들은 무엇인가요?
표현되지 못하고 쌓여 있는 마음들은 무엇인가요?
충분히 표현했다는 마음이 들 때까지 자유롭게 기록합니다.
이전 단계
{isComplete ? '다음 단계로' : '글을 작성해주세요'}
);
};
// Step 4 Component
const Step4 = ({ data, onUpdate, onNext, onPrev }) => {
const [activeTab, setActiveTab] = useState('signal');
const [showNeedsModal, setShowNeedsModal] = useState(false);
const needsList = {
'자율성': ['자신의 목표/가치 선택', '꿈을 이루기 위한 방법 선택'],
'신체적/생존': ['공기', '음식', '물', '주거', '휴식', '수면', '안전', '신체적 접촉', '운동'],
'사회적/정서적': ['소통', '연결', '존중', '공감', '이해', '수용', '지지', '협력', '인정', '사랑', '소속감', '공동체', '신뢰'],
'놀이/재미': ['즐거움', '재미', '유머'],
'삶의 의미': ['기여', '성장', '배움', '보람', '깨달음', '참여', '효능감', '희망'],
'진실성': ['정직', '진실', '자기존중'],
'아름다움/평화': ['아름다움', '평온함', '조화', '질서'],
};
const handleUpdate = (field, value) => onUpdate('step4', { ...data.step4, [field]: value });
const handleNeedClick = (need) => {
const currentNeeds = data.step4?.needs || '';
handleUpdate('needs', currentNeeds ? `${currentNeeds}, ${need}` : need);
};
const isComplete = data.step4?.signal_self && data.step4?.signal_env && data.step4?.needs && data.step4?.needs_solution && data.step4?.purpose_help && data.step4?.purpose_newName;
const TabButton = ({ id, label }) => (
setActiveTab(id)}
className={`flex-1 px-4 py-3 font-bold text-center transition-colors rounded-t-2xl ${activeTab === id ? 'bg-white text-orange-500' : 'bg-orange-100 text-orange-400 hover:bg-orange-50'}`}
>
{label}
);
return (
4단계: 감정 이해하기
감정이 보내는 신호를 해석하고, 그 안에 숨겨진 필요와 목적을 발견합니다.
{activeTab === 'signal' && (
1. 이 감정은 '나 자신'에 대해서 어떤 신호를 보내고 있나요?
2. 이 감정은 '나의 삶과 환경'에 대해 어떤 신호를 보내고 있나요?
)}
{activeTab === 'needs' && (
2. 그 필요를 건강하게 채울 수 있는 방법은 무엇일까요?
)}
{activeTab === 'purpose' && (
)}
setShowNeedsModal(false)} title="욕구 목록">
{Object.entries(needsList).map(([category, items]) => (
{category}
{items.map(item => (
handleNeedClick(item)} className="px-3 py-1 bg-orange-100 text-orange-800 rounded-full text-sm hover:bg-orange-200">{item}
))}
))}
setShowNeedsModal(false)} className="px-6 py-3 bg-orange-500 text-white rounded-full font-bold">닫기
이전 단계
{isComplete ? '다음 단계로' : '모든 항목을 채워주세요'}
);
};
// Step 5 Component
const Step5 = ({ data, onUpdate, onNext, onPrev }) => {
const handleUpdate = (field, value) => onUpdate('step5', { ...data.step5, [field]: value });
useEffect(() => {
if (data.step4?.purpose_newName && !data.step5?.final_newName) {
handleUpdate('final_newName', data.step4.purpose_newName);
}
}, [data.step4?.purpose_newName]);
const isComplete = data.step5?.message_to_emotion && data.step5?.final_newName && data.step5?.final_purpose && data.step5?.final_action;
return (
5단계: 감정 소통하기
나의 감정에게 말을 걸어보고, 감정의 진짜 목적을 다시 한번 확인합니다.
5-1. 내가 감정에게 해주고 싶은 이야기
그 감정을 느끼는 나에게, 혹은 감정 그 자체에게 해주고 싶은 말을 진심을 담아 적어보세요.
5-2. 감정과의 약속
감정과의 소통을 통해 발견한 내용을 바탕으로 앞으로의 다짐을 정리해봅니다.
이전 단계
{isComplete ? '다음 단계로' : '모든 항목을 채워주세요'}
);
};
// Step 6 Component
const Step6 = ({ data, onUpdate, onNext, onPrev }) => {
const handleUpdate = (field, value) => onUpdate('step6', { ...data.step6, [field]: value });
const isComplete = data.step6?.drawingB && data.step6?.drawingBName;
return (
6단계: 감정 정리하기
나의 다짐을 확인하고, 변화된 감정을 다시 한번 그려봅니다.
6-1. 나의 다짐 확인하기
감정의 새로운 이름
{data.step5?.final_newName || '아직 정해지지 않았습니다.'}
감정이 이루고자 하는 진짜 목적
{data.step5?.final_purpose || '아직 정해지지 않았습니다.'}
이 감정이 찾아올 때 나를 위해 할 수 있는 일
{data.step5?.final_action || '아직 정해지지 않았습니다.'}
이전 단계
{isComplete ? '워크숍 완료하기' : '그림을 그리고 이름을 붙여주세요'}
);
};
// Final Dashboard Component
const FinalDashboard = ({ data, onRestart }) => {
const dashboardRef = useRef(null);
const [showErrorModal, setShowErrorModal] = useState(false);
// Download dashboard as an image using html2canvas
const downloadDashboard = () => {
if (dashboardRef.current && typeof window.html2canvas === 'function') {
window.html2canvas(dashboardRef.current, {
useCORS: true,
scale: 2,
backgroundColor: '#FFF7ED'
}).then(canvas => {
const link = document.createElement('a');
link.download = '나의_감정워크북_결과.png';
link.href = canvas.toDataURL('image/png');
link.click();
}).catch(err => {
console.error("html2canvas error:", err);
setShowErrorModal(true);
});
} else {
console.error("html2canvas function not found or dashboard ref is not set.");
setShowErrorModal(true);
}
};
return (
<>
감정 워크숍 여정을 마쳤습니다!
자신의 감정을 마주하고 이해하려 노력한 당신에게 박수를 보냅니다.
나의 감정 여정 요약
Before: 처음 만난 감정
{data.step1?.drawingA ?
:
그림 없음
}
"{data.step1?.drawingAName || '이름 없음'}"
After: 변화된 감정
{data.step6?.drawingB ?
:
그림 없음
}
"{data.step6?.drawingBName || '이름 없음'}"
나의 감정 선언문
나는 "{data.step1?.topic || '정했던'}" 이라는 감정을 통해,
나에게 "{data.step5?.final_purpose || '중요한 목적'}" 이 있음을 발견했습니다.
이 감정은 이제 나에게 "{data.step5?.final_newName || '새로운 이름'}" (으)로 불릴 것이며,
앞으로 이 감정이 찾아올 때 나는 나를 위해 "{data.step5?.final_action || '정해진 행동'}" 을/를 할 것을 약속합니다.
setShowErrorModal(false)} title="오류">
결과를 이미지로 저장하는 기능을 현재 사용할 수 없습니다.
페이지를 새로고침한 후 다시 시도해 주세요.
setShowErrorModal(false)} className="px-6 py-2 bg-orange-500 text-white rounded-full">확인
>
);
};
// Main App Component
export default function App() {
const [currentStep, setCurrentStep] = useState(0); // Current step
const [userData, setUserData] = useState({}); // User data
const [userId, setUserId] = useState(null); // Firebase user ID
const [isLoading, setIsLoading] = useState(true); // Loading state
const [showRestartConfirm, setShowRestartConfirm] = useState(false); // Restart confirmation modal
// Dynamically load html2canvas script
useEffect(() => {
const scriptId = 'html2canvas-script';
if (document.getElementById(scriptId)) return;
const script = document.createElement('script');
script.id = scriptId;
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
script.async = true;
document.body.appendChild(script);
return () => {
const scriptElement = document.getElementById(scriptId);
if (scriptElement && scriptElement.parentNode) {
scriptElement.parentNode.removeChild(scriptElement);
}
};
}, []);
// Set up Firebase auth state listener
useEffect(() => {
const authListener = onAuthStateChanged(auth, async (user) => {
if (user) {
setUserId(user.uid);
} else {
try {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
} catch (error) {
console.error("Authentication error:", error);
setIsLoading(false);
}
}
});
return () => authListener();
}, []);
// Real-time data synchronization with Firestore
useEffect(() => {
if (!userId) return;
const docRef = doc(db, "artifacts", appId, "users", userId, "data", "emotionWorkbook");
const unsubscribe = onSnapshot(docRef, (docSnap) => {
if (docSnap.exists()) {
const loadedData = docSnap.data();
setUserData(loadedData.data || {});
setCurrentStep(loadedData.step || 0);
} else {
setUserData({});
setCurrentStep(0);
}
setIsLoading(false);
}, (error) => {
console.error("Firestore snapshot error:", error);
setIsLoading(false);
});
return () => unsubscribe();
}, [userId]);
// Save data to Firestore
const saveData = async (step, data) => {
if (!userId) return;
try {
const docRef = doc(db, "artifacts", appId, "users", userId, "data", "emotionWorkbook");
await setDoc(docRef, { step, data });
} catch (error) {
console.error("Error saving data:", error);
}
};
// Update data handler
const handleUpdate = (stepKey, stepData) => {
const updatedData = { ...userData, [stepKey]: stepData };
setUserData(updatedData);
saveData(currentStep, updatedData);
};
// Move to the next step
const nextStep = () => {
const next = Math.min(currentStep + 1, 7);
setCurrentStep(next);
saveData(next, userData);
};
// Move to the previous step
const prevStep = () => {
const prev = Math.max(currentStep - 1, 0);
setCurrentStep(prev);
saveData(prev, userData);
};
// Restart workbook handler
const handleRestart = () => setShowRestartConfirm(true);
const confirmRestart = () => {
setUserData({});
setCurrentStep(1); // Start from step 1
saveData(1, {});
setShowRestartConfirm(false);
};
// Render the current step component
const renderStep = () => {
switch (currentStep) {
case 1: return
;
case 2: return
;
case 3: return
;
case 4: return
;
case 5: return
;
case 6: return
;
case 7: return
;
default:
return ( // Start screen
디지털 감정 워크북
나의 소화되지 않는 감정을 마주하고 이해하는 시간을 가져보세요.
{ setCurrentStep(1); saveData(1, userData); }} className="px-12 py-5 bg-orange-500 text-white font-bold rounded-full text-xl hover:bg-orange-600 transition-all duration-300 shadow-xl hover:shadow-2xl transform hover:-translate-y-1">
나의 감정 여정 시작하기
);
}
};
// Progress indicator
const ProgressIndicator = () => {
if (currentStep < 1 || currentStep > 6) return null;
const totalSteps = 6;
const progress = (currentStep / totalSteps) * 100;
return (
)
}
// Loading screen
if (isLoading) {
return (
);
}
return (
{currentStep > 0 && currentStep < 7 && (
prevStep()} className="text-sm font-bold text-gray-500 hover:text-orange-500 transition-colors">← 이전으로
{currentStep} / 6 단계
)}
{renderStep()}
setShowRestartConfirm(false)}
title="새로 시작하기"
>
정말로 모든 진행 상황을 초기화하고 새로 시작하시겠습니까? 저장된 데이터는 영구적으로 사라집니다.
setShowRestartConfirm(false)} className="px-6 py-2 bg-gray-200 text-gray-700 font-bold rounded-full">취소
확인
);
}