상세 컨텐츠

본문 제목

firebase 방명록 앱 제작을 위한 html 코드

AI 디지털 혁신/수업템 자료배포

by shinypeace 2025. 6. 18. 09:15

본문

아래 코드를 읽어보고 한글로 된 부분을 바꿀 수 있어요.

문구를 바꾸어 코드를 적용해보세요

*반드시 바꿔야 하는 부분
1) 본인의 SDK 스크립트로 변경(firebase 콘솔의 프로젝트 설정에서 변경)
2) 방명록을 초기화하는 비밀번호 변경(현재는 1234)

3) 기타 제목이나 문구 등 변경(선택사항)

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firebase 우리들의 방명록 (수정/삭제)</title>
    <!-- Tailwind CSS CDN 로드 -->
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        /* 귀여운 글꼴 'Gamja Flower' 적용 */
       
        body {
            font-family: 'Gamja Flower', cursive;
        }

        /* 몽글몽글한 그라데이션 배경 애니메이션 */
        @keyframes gradient-animation {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }

        .animated-gradient {
            background: linear-gradient(-45deg, #fce4ec, #f8bbd0, #e1f5fe, #b3e5fc);
            background-size: 400% 400%;
            animation: gradient-animation 15s ease infinite;
        }
       
        /* 메시지가 뿅 나타나는 애니메이션 */
        @keyframes pop-in {
            0% {
                opacity: 0;
                transform: translateY(20px) scale(0.9);
            }
            100% {
                opacity: 1;
                transform: translateY(0) scale(1);
            }
        }
       
        .message-pop-in {
            animation: pop-in 0.5s ease-out forwards;
        }

        /* 스크롤바 디자인 */
        ::-webkit-scrollbar {
            width: 8px;
        }
        ::-webkit-scrollbar-track {
            background: #fce4ec; /* 연분홍 트랙 */
        }
        ::-webkit-scrollbar-thumb {
            background: #f48fb1; /* 핑크색 핸들 */
            border-radius: 4px;
        }
        ::-webkit-scrollbar-thumb:hover {
            background: #f06292;
        }
    </style>
</head>
<body class="animated-gradient flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-md mx-auto bg-white/80 backdrop-blur-sm rounded-3xl shadow-2xl p-6 sm:p-8 transition-all duration-500">
        <header class="mb-6 text-center">
            <h1 class="text-4xl font-bold text-pink-500">🏖️ 우리들의 방명록 ☀️</h1>
            <p class="text-gray-600 text-lg mt-2">너의 이야기를 들려줘! 🍦✈️👙</p>
        </header>

        <!-- 메시지 표시 영역 -->
        <main id="messages-container" class="h-80 overflow-y-auto bg-white/70 p-4 rounded-2xl mb-6 border border-pink-100 space-y-4"></main>

        <!-- 메시지 입력 폼 -->
        <form id="message-form" class="flex gap-3">
            <input
                id="nickname-input"
                type="text"
                placeholder="닉네임"
                class="w-1/4 px-4 py-3 border-2 border-pink-200 bg-white/80 rounded-full focus:outline-none focus:ring-4 focus:ring-pink-300 focus:border-pink-400 transition text-gray-700 placeholder-gray-500"
                required
            >
            <input
                id="message-input"
                type="text"
                placeholder="어떤 이야기를 들려줄래?"
                class="flex-1 px-4 py-3 border-2 border-pink-200 bg-white/80 rounded-full focus:outline-none focus:ring-4 focus:ring-pink-300 focus:border-pink-400 transition text-gray-700 placeholder-gray-500"
                required
            >
            <button
                type="submit"
                class="bg-pink-500 text-white font-bold px-5 py-3 rounded-full hover:bg-pink-600 focus:outline-none focus:ring-4 focus:ring-pink-300 transition-all duration-300 transform hover:scale-110 active:scale-95"
            >
                전송 뿅!
            </button>
        </form>

        <!-- 전체 삭제 버튼 추가 -->
        <div class="mt-6 text-center">
            <button id="clear-all-button" class="bg-red-500 text-white text-sm font-bold px-4 py-2 rounded-full hover:bg-red-600 transition-transform transform active:scale-95">
                전체 초기화 🔥
            </button>
        </div>
    </div>

    <!-- Firebase SDK 스크립트 -->
    <script type="module">
        // Firebase v11 SDK 불러오기
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getFirestore, collection, addDoc, query, onSnapshot, doc, deleteDoc, updateDoc, getDocs } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { getAuth, signInAnonymously, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";

        // Firebase 프로젝트 설정
        const firebaseConfig = {
            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"
        };

        // Firebase 앱 및 서비스 초기화
        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);

        // UI 요소 가져오기
        const messagesContainer = document.getElementById('messages-container');
        const messageForm = document.getElementById('message-form');
        const nicknameInput = document.getElementById('nickname-input');
        const messageInput = document.getElementById('message-input');
        const clearAllButton = document.getElementById('clear-all-button');

        let currentUser = null;

        // 사용자의 인증 상태 변경을 감지
        onAuthStateChanged(auth, (user) => {
            if (user) {
                // 사용자가 (익명으로) 로그인한 경우
                currentUser = user;
                listenForMessages(); // 메시지 실시간 수신 시작
            } else {
                // 사용자가 로그아웃한 경우
                currentUser = null;
                messagesContainer.innerHTML = '<p class="text-center text-gray-500">로그인이 필요해요!</p>';
            }
        });

        // 메시지를 실시간으로 가져와 화면에 표시하는 함수
        function listenForMessages() {
            const q = query(collection(db, "guestbook"));
           
            onSnapshot(q, (querySnapshot) => {
                messagesContainer.innerHTML = '';
                if (querySnapshot.empty) {
                    messagesContainer.innerHTML = '<p class="text-center text-gray-500 text-lg">아직 아무도 글을 안썼어.. 힝 🥺<br>첫 번째 메시지를 남겨볼까?</p>';
                    return;
                }
               
                querySnapshot.forEach((docSnap) => {
                    const messageData = docSnap.data();
                    const messageId = docSnap.id;
                   
                    const messageElement = document.createElement('div');
                    messageElement.classList.add('p-4', 'rounded-2xl', 'bg-gradient-to-br', 'from-sky-100', 'to-blue-200', 'text-gray-800', 'break-words', 'shadow-md', 'message-pop-in');
                   
                    // 내가 쓴 글인지 확인하고, 맞다면 수정/삭제 버튼 추가
                    const isMyMessage = currentUser && messageData.uid === currentUser.uid;
                    const buttonsHTML = isMyMessage ? `
                        <div class="text-right mt-2">
                            <button class="edit-btn text-xs bg-yellow-400 text-white px-2 py-1 rounded-md hover:bg-yellow-500" data-id="${messageId}">수정</button>
                            <button class="delete-btn text-xs bg-red-400 text-white px-2 py-1 rounded-md hover:bg-red-500" data-id="${messageId}">삭제</button>
                        </div>
                    ` : '';

                    messageElement.innerHTML = `
                        <p class="font-bold text-lg text-sky-800">${escapeHTML(messageData.nickname)} 👑</p>
                        <p class="message-text text-base mt-1">${escapeHTML(messageData.text)}</p>
                        <p class="text-xs text-right text-gray-500 mt-2">${new Date(messageData.timestamp).toLocaleString()}</p>
                        ${buttonsHTML}
                    `;
                    messagesContainer.appendChild(messageElement);
                });
                messagesContainer.scrollTop = messagesContainer.scrollHeight;
            });
        }

        // 메시지 전송 (폼 제출) 이벤트 처리
        messageForm.addEventListener('submit', async (e) => {
            e.preventDefault();
            if (!currentUser) {
                alert('앗! 로그인이 필요해~');
                return;
            }
            const nickname = nicknameInput.value;
            const text = messageInput.value;

            if (!nickname.trim() || !text.trim()) {
                alert('앗! 닉네임과 메시지를 모두 적어줘~');
                return;
            }

            try {
                await addDoc(collection(db, "guestbook"), {
                    nickname: nickname,
                    text: text,
                    timestamp: Date.now(),
                    uid: currentUser.uid // 글쓴이의 고유 ID 저장
                });
                messageInput.value = '';
                messageInput.focus();
            } catch (error) {
                console.error("메시지 추가 오류: ", error);
            }
        });

        // 메시지 컨테이너에서 발생하는 클릭 이벤트를 처리 (이벤트 위임)
        messagesContainer.addEventListener('click', async (e) => {
            const docId = e.target.dataset.id;
            if (!docId) return;

            // 삭제 버튼을 클릭한 경우
            if (e.target.classList.contains('delete-btn')) {
                if (confirm('정말로 이 메시지를 삭제할까요?')) {
                    const docRef = doc(db, 'guestbook', docId);
                    await deleteDoc(docRef);
                }
            }

            // 수정 버튼을 클릭한 경우
            if (e.target.classList.contains('edit-btn')) {
                const newText = prompt('메시지를 어떻게 수정할까요?');
                if (newText !== null && newText.trim() !== '') {
                    const docRef = doc(db, 'guestbook', docId);
                    await updateDoc(docRef, { text: newText });
                }
            }
        });
       
        // [수정됨] 전체 초기화 버튼 클릭 시 비밀번호 확인
        clearAllButton.addEventListener('click', async () => {
            const password = prompt('관리자 비밀번호를 입력하세요.');

            // 비밀번호가 맞는지 확인 (실제 앱에서는 더 안전한 방법을 사용해야 합니다)
            if (password === '1234') {
                if (confirm('⚠️ 정말로 모든 메시지를 삭제할까요? 이 작업은 되돌릴 수 없어요!')) {
                    const q = query(collection(db, "guestbook"));
                    const querySnapshot = await getDocs(q);
                    // 모든 문서를 병렬로 삭제하기 위한 프로미스 배열
                    const deletePromises = [];
                    querySnapshot.forEach((docSnap) => {
                        deletePromises.push(deleteDoc(doc(db, 'guestbook', docSnap.id)));
                    });
                    // 모든 삭제 작업이 완료될 때까지 기다림
                    await Promise.all(deletePromises);
                    alert('모든 메시지가 성공적으로 삭제되었습니다.');
                }
            } else if (password !== null) { // 사용자가 취소를 누르지 않았을 때만
                alert('비밀번호가 틀렸습니다!');
            }
        });

        // XSS(Cross-Site Scripting) 공격 방지를 위한 HTML 태그 치환 함수
        function escapeHTML(str) {
            const p = document.createElement("p");
            p.appendChild(document.createTextNode(str));
            return p.innerHTML;
        }

        // 앱 시작 시 익명으로 로그인
        signInAnonymously(auth).catch((error) => {
            console.error("익명 로그인 실패:", error);
        });

    </script>
</body>
</html>

 

관련글 더보기