설정 제한 보정

This commit is contained in:
2026-04-07 15:10:43 +09:00
parent 923a9af83d
commit 51170b2ff7
7 changed files with 63 additions and 21 deletions

View File

@@ -61,13 +61,17 @@ const displayInitial = computed(() => displayInitialFrom(auth.user?.nickname, au
const authEmail = computed(() => auth.user?.email || '')
const nicknameUpdatedAt = computed(() => Number(auth.user?.nicknameUpdatedAt || 0))
const nicknameChangeAvailableAt = computed(() => Number(auth.user?.nicknameChangeAvailableAt || 0))
const nicknameChangeIntervalMs = computed(() => Number(auth.user?.nicknameChangeIntervalMs || 0))
const nicknameChangeIntervalLabel = computed(() => String(auth.user?.nicknameChangeIntervalLabel || '2주'))
const hasPendingAvatarChange = computed(() => !!avatarFile.value || removeAvatar.value)
const canChangeNicknameNow = computed(() => {
if (nicknameChangeIntervalMs.value <= 0) return true
if (!nicknameUpdatedAt.value) return true
return Date.now() >= nicknameChangeAvailableAt.value
})
const nicknameCooldownText = computed(() => {
if (!nicknameUpdatedAt.value || canChangeNicknameNow.value) return '닉네임은 2주에 한 번만 변경할 수 있어요.'
if (nicknameChangeIntervalMs.value <= 0) return '닉네임 변경 제한이 없습니다.'
if (!nicknameUpdatedAt.value || canChangeNicknameNow.value) return `닉네임은 ${nicknameChangeIntervalLabel.value}에 한 번만 변경할 수 있어요.`
return `다음 변경 가능 시점: ${formatDateTime(nicknameChangeAvailableAt.value)}`
})
const profileImageSummary = computed(() => {
@@ -203,7 +207,8 @@ async function saveProfile(nextNickname = nickname.value) {
nicknameDraftError.value = nicknameError.value
error.value = '사용할 수 없는 닉네임이에요.'
} else if (code === 'nickname_change_locked') {
nicknameDraftError.value = `닉네임은 2주에 한 번만 바꿀 수 있어요. ${formatDateTime(e2?.data?.nicknameChangeAvailableAt)} 이후 다시 시도해주세요.`
const intervalLabel = String(e2?.data?.nicknameChangeIntervalLabel || nicknameChangeIntervalLabel.value || '2주')
nicknameDraftError.value = `닉네임은 ${intervalLabel}에 한 번만 바꿀 수 있어요. ${formatDateTime(e2?.data?.nicknameChangeAvailableAt)} 이후 다시 시도해주세요.`
error.value = '닉네임 변경 가능 시점이 아직 아니에요.'
} else {
error.value = '프로필 저장에 실패했어요.'
@@ -322,7 +327,6 @@ async function logout() {
<article class="settingsThemeCard settingsThemeCard--hero">
<div class="settingsThemeCard__eyebrow">Profile</div>
<div class="settingsThemeCard__title">기본 계정 정보</div>
<div class="settingsThemeCard__desc">아바타, 닉네임, 이메일처럼 자주 바뀌지 않는 정보는 현재 상태만 먼저 보여주고 필요한 순간에만 열어 수정합니다.</div>
<div class="settingsHero">
<div class="settingsAvatarColumn">
@@ -346,8 +350,8 @@ async function logout() {
</svg>
</button>
</div>
<div v-if="auth.user.isAdmin" class="roleBadge">Administrator</div>
<input ref="fileInput" class="hiddenInput" type="file" accept="image/*" :disabled="saving" @change="onAvatarChange" />
<button class="btn btn--ghost settingsInlineAction" type="button" @click="openAvatarPicker">프로필 이미지 변경</button>
</div>
<div class="settingsHero__body">
@@ -378,7 +382,7 @@ async function logout() {
<button v-if="hasPendingAvatarChange" class="btn btn--save" type="button" :disabled="saving" @click="saveAvatarChanges">
{{ saving ? '저장 중...' : '프로필 이미지 저장' }}
</button>
<div v-if="auth.user.isAdmin" class="roleBadge">Administrator</div>
</div>
</div>
</div>
@@ -387,7 +391,6 @@ async function logout() {
<article class="settingsThemeCard">
<div class="settingsThemeCard__eyebrow">Security</div>
<div class="settingsThemeCard__title">보안 설정</div>
<div class="settingsThemeCard__desc">비밀번호는 자주 노출할 필요가 없으니, 필요할 때만 열리는 작은 액션으로 정리했습니다.</div>
<div class="settingsCompactRow">
<div>
<div class="settingsCompactRow__title">비밀번호</div>
@@ -400,7 +403,6 @@ async function logout() {
<article class="settingsThemeCard">
<div class="settingsThemeCard__eyebrow">Session</div>
<div class="settingsThemeCard__title">계정 상태</div>
<div class="settingsThemeCard__desc">지금 로그인한 계정 정보를 확인하고 세션을 정리할 있어요.</div>
<div class="settingsCompactList">
<div class="settingsCompactRow">
<div>
@@ -423,7 +425,10 @@ async function logout() {
<div v-if="activeModal === 'nickname'" class="settingsModalOverlay" @click.self="closeNicknameModal">
<div class="settingsModalCard" role="dialog" aria-modal="true" aria-labelledby="nicknameModalTitle">
<div id="nicknameModalTitle" class="settingsModalCard__title">닉네임 변경</div>
<div class="settingsModalCard__desc">닉네임은 공개 티어표의 작성자 이름으로 보이며, 악용 방지를 위해 2주에 번만 변경할 있어요.</div>
<div class="settingsModalCard__desc">
닉네임은 공개 티어표의 작성자 이름으로 보이며,
{{ nicknameChangeIntervalMs > 0 ? `악용 방지를 위해 ${nicknameChangeIntervalLabel}에 한 번만 변경할 수 있어요.` : '현재는 변경 주기 제한이 없습니다.' }}
</div>
<label class="field">
<span class="field__label"> 닉네임</span>
<input ref="nicknameInput" v-model="nicknameDraft" class="field__input" maxlength="40" placeholder="표시용 닉네임" />
@@ -781,11 +786,6 @@ async function logout() {
cursor: default;
}
.settingsInlineAction {
width: 100%;
justify-content: center;
}
.btn {
display: inline-flex;
align-items: center;