feat(profile): 프로필에 '한줄 소개' 추가 — 본인만 인라인 편집
- users.bio 컬럼(멱등) + db.setBio / getUserProfile 조회 포함 - POST /profile/bio (본인만, fetch JSON / 폼 POST 폴백) - 프로필 히어로: 이름·사번 한 줄로 합치고 한줄 소개를 3줄째로 배치 - 본인은 연필 버튼으로 인라인 편집(저장/취소·Esc), 최대 80자 - word-break: keep-all 로 한글 단어 중간 잘림 방지 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -157,9 +157,20 @@
|
||||
.profile-avatar-edit { position: absolute; right: -4px; bottom: -4px; width: 30px; height: 30px; border-radius: 50%; border: 2px solid var(--card); background: var(--link); color: #fff; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px var(--shadow); }
|
||||
.profile-avatar-edit:hover { filter: brightness(1.08); }
|
||||
.profile-info { min-width: 0; }
|
||||
.profile-idline { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
|
||||
.profile-name { margin: 0; font-size: 24px; font-weight: 800; color: var(--fg); letter-spacing: -.01em; }
|
||||
.profile-sabun { margin-top: 3px; font-size: 13px; color: var(--fg2); font-variant-numeric: tabular-nums; }
|
||||
.profile-metaline { margin-top: 8px; display: flex; gap: 6px; font-size: 12.5px; color: var(--fg3); }
|
||||
.profile-sabun { font-size: 13px; color: var(--fg2); font-variant-numeric: tabular-nums; }
|
||||
.profile-metaline { margin-top: 6px; display: flex; gap: 6px; font-size: 12.5px; color: var(--fg3); }
|
||||
|
||||
/* 한줄 소개 */
|
||||
.profile-bio-row { margin-top: 10px; display: flex; align-items: center; gap: 8px; }
|
||||
.profile-bio { margin: 0; font-size: 13.5px; color: var(--fg2); line-height: 1.5; word-break: keep-all; overflow-wrap: break-word; }
|
||||
.profile-bio.is-empty { color: var(--fg3); font-style: italic; }
|
||||
.profile-bio-edit { flex: 0 0 auto; width: 24px; height: 24px; border-radius: 6px; border: 1px solid var(--border); background: var(--card); color: var(--fg3); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
|
||||
.profile-bio-edit:hover { color: var(--fg); border-color: var(--fg3); }
|
||||
.profile-bio-form { margin-top: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.profile-bio-form input { flex: 1 1 240px; min-width: 0; padding: 6px 10px; font-size: 13.5px; color: var(--fg); background: var(--card); border: 1px solid var(--border); border-radius: 8px; }
|
||||
.profile-bio-form input:focus { outline: none; border-color: var(--link); }
|
||||
|
||||
/* ===================== 프로필 사진 크롭 모달 ===================== */
|
||||
.crop-modal { width: 100%; max-width: 420px; margin: auto; background: var(--card); border: 1px solid var(--border); border-radius: 14px; box-shadow: 0 16px 48px var(--shadow-strong); overflow: hidden; animation: aidev-pop .18s ease-out; }
|
||||
|
||||
@@ -4,7 +4,53 @@ import { $, toast } from "./util.js";
|
||||
|
||||
const OUT = 256; // 저장 이미지 한 변(px)
|
||||
|
||||
// 한줄 소개 편집 — 텍스트/편집버튼을 폼으로 토글, 저장은 fetch 로 즉시 반영.
|
||||
function initBio() {
|
||||
const row = $("#bioRow");
|
||||
const form = $("#bioForm");
|
||||
const text = $("#bioText");
|
||||
const editBtn = $("#bioEditBtn");
|
||||
const input = $("#bioInput");
|
||||
const cancel = $("#bioCancel");
|
||||
if (!row || !form || !text || !editBtn || !input) return; // 본인 프로필에서만 동작
|
||||
|
||||
function show(editing) {
|
||||
row.hidden = editing;
|
||||
form.hidden = !editing;
|
||||
if (editing) { input.focus(); input.select(); }
|
||||
}
|
||||
|
||||
editBtn.addEventListener("click", () => show(true));
|
||||
if (cancel) cancel.addEventListener("click", () => { input.value = text.classList.contains("is-empty") ? "" : text.textContent; show(false); });
|
||||
input.addEventListener("keydown", (e) => { if (e.key === "Escape") { e.preventDefault(); cancel.click(); } });
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const bio = input.value.trim().slice(0, 80);
|
||||
const saveBtn = $("#bioSave");
|
||||
if (saveBtn) { saveBtn.disabled = true; saveBtn.textContent = "저장 중…"; }
|
||||
try {
|
||||
const res = await fetch("/profile/bio", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Requested-With": "fetch" },
|
||||
body: JSON.stringify({ bio }),
|
||||
});
|
||||
if (!res.ok) throw new Error("save failed");
|
||||
text.textContent = bio || "한줄 소개를 남겨보세요";
|
||||
text.classList.toggle("is-empty", !bio);
|
||||
show(false);
|
||||
toast("한줄 소개가 저장됐어요");
|
||||
} catch (err) {
|
||||
toast("저장에 실패했어요");
|
||||
} finally {
|
||||
if (saveBtn) { saveBtn.disabled = false; saveBtn.textContent = "저장"; }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function initProfile() {
|
||||
initBio();
|
||||
|
||||
const editBtn = $("#avatarEditBtn");
|
||||
const modal = $("#cropModal");
|
||||
if (!editBtn || !modal) return; // 본인 프로필이 아니면 크롭 UI 없음
|
||||
|
||||
Reference in New Issue
Block a user