2024 라이프처치 감사릴레이
한 해 동안 하나님께 감사했던 것들, 그리고 라이프처치 성도들과 주변 사람들에게 감사했던 것들을 고백해보세요.
내용은 익명으로 접수되며, 여러분의 마음이 하나 하나 모일 때, 풍성하고 따뜻한 추수감사주일을 함께 보낼 수 있습니다.
- 글번호
- 제목
- 글쓴이
- 작성시간
- 조회
import React, { useEffect, useMemo, useRef, useState } from "react";
// 단일 파일 React 컴포넌트 (백엔드 없이 로컬 저장)
// 기능: 글 작성, 좋아요, 댓글, 삭제, 검색, 정렬, 로컬스토리지 영속화
// Tailwind 사용 (별도 import 불필요)
const STORAGE_KEY = "mini_social_board_v1";
function uid() {
return Math.random().toString(36).slice(2) + Date.now().toString(36);
}
function timeAgo(dateNum) {
const sec = Math.floor((Date.now() - dateNum) / 1000);
if (sec < 60) return `${sec}초 전`;
const m = Math.floor(sec / 60);
if (m < 60) return `${m}분 전`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}시간 전`;
const d = Math.floor(h / 24);
if (d < 7) return `${d}일 전`;
const dt = new Date(dateNum);
return `${dt.getFullYear()}.${String(dt.getMonth() + 1).padStart(2, "0")}.${String(
dt.getDate()
).padStart(2, "0")} ${String(dt.getHours()).padStart(2, "0")}:${String(
dt.getMinutes()
).padStart(2, "0")}`;
}
function classNames(...a) {
return a.filter(Boolean).join(" ");
}
const HeartIcon = ({ filled }) => (
);
const MessageIcon = () => (
);
const TrashIcon = () => (
);
const DotsIcon = () => (
);
export default function SocialBoard() {
const [posts, setPosts] = useState([]);
const [author, setAuthor] = useState("");
const [content, setContent] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [query, setQuery] = useState("");
const [sort, setSort] = useState("new"); // new | top
const contentRef = useRef(null);
// 초기 로드
useEffect(() => {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) setPosts(JSON.parse(raw));
} catch {}
}, []);
// 저장
useEffect(() => {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(posts));
} catch {}
}, [posts]);
// textarea 자동 높이
useEffect(() => {
const el = contentRef.current;
if (!el) return;
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
}, [content]);
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
let arr = posts;
if (q) {
arr = arr.filter(
(p) =>
p.content.toLowerCase().includes(q) ||
p.author.toLowerCase().includes(q) ||
p.comments?.some((c) => c.content.toLowerCase().includes(q))
);
}
if (sort === "new") {
arr = [...arr].sort((a, b) => b.createdAt - a.createdAt);
} else if (sort === "top") {
arr = [...arr].sort((a, b) => b.likes - a.likes || b.createdAt - a.createdAt);
}
return arr;
}, [posts, query, sort]);
function addPost(e) {
e?.preventDefault?.();
const body = content.trim();
if (!body) return;
const post = {
id: uid(),
author: author.trim() || "익명",
content: body,
imageUrl: imageUrl.trim(),
createdAt: Date.now(),
likes: 0,
likedByMe: false,
comments: [],
};
setPosts((p) => [post, ...p]);
setContent("");
setImageUrl("");
}
function toggleLike(id) {
setPosts((prev) =>
prev.map((p) =>
p.id === id
? {
...p,
likedByMe: !p.likedByMe,
likes: p.likedByMe ? Math.max(0, p.likes - 1) : p.likes + 1,
}
: p
)
);
}
function addComment(postId, authorName, text) {
const t = text.trim();
if (!t) return;
setPosts((prev) =>
prev.map((p) =>
p.id === postId
? {
...p,
comments: [
...(p.comments || []),
{ id: uid(), author: authorName.trim() || "익명", content: t, createdAt: Date.now() },
],
}
: p
)
);
}
function deletePost(id) {
if (!confirm("이 게시글을 삭제하시겠습니까?")) return;
setPosts((prev) => prev.filter((p) => p.id !== id));
}
return (
);
}
function CommentComposer({ onSubmit }) {
const [name, setName] = useState("");
const [text, setText] = useState("");
const inputRef = useRef(null);
function handleSubmit(e) {
e.preventDefault();
if (!text.trim()) return;
onSubmit?.(name, text);
setText("");
}
return (
);
}
{/* 헤더 */}
{/* 작성 영역 */}
{/* 검색 */}
Mini Social Board
setQuery(e.target.value)}
className="w-full rounded-xl border bg-white px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring"
/>
{/* 피드 */}
-
{filtered.map((post) => (
-
{post.author} · {timeAgo(post.createdAt)}{post.content}{post.imageUrl && ({/* eslint-disable-next-line @next/next/no-img-element */})}{/* 댓글 입력 */}
댓글 {post.comments?.length || 0} addComment(post.id, name, text)} /> {/* 댓글 목록 */} {post.comments?.length > 0 && ( -
{post.comments.map((c) => (
-
{c.author} · {timeAgo(c.createdAt)}{c.content}
))}
))}
{filtered.length === 0 && (
-
- 아직 게시글이 없습니다. 첫 글을 남겨보세요! )}