feat: 웹 안내페이지(앱전용)·회원가입 제한/봇차단·카드 인식 보정

- 웹(브라우저)은 안내 페이지만, 실제 이용은 앱(Capacitor). 개발모드는 예외
- 회원가입: 약관동의, 관리자 제한 토글, 진입부터 차단, 허니팟
- 카드 알림: pending 건 계좌종류 '카드' 기본선택(현금 X), 확인실패 시 HTTP 상태 표시
- 네이티브 알림필터: '카드' 키워드 제거+광고성 표현 차단
- docs/release-2026-06-06.md 정리

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-06 00:16:50 +09:00
parent 4e6a89fd7a
commit c5e4c2dad7
12 changed files with 386 additions and 27 deletions
+40 -3
View File
@@ -7,7 +7,8 @@ import { TERMS_OF_SERVICE, PRIVACY_POLICY } from '@/constants/terms'
const auth = useAuthStore()
const ui = useUiStore()
const form = reactive({ loginId: '', password: '', passwordConfirm: '', name: '', email: '' })
// website = 허니팟(숨김 필드, 봇이 채우면 서버에서 차단)
const form = reactive({ loginId: '', password: '', passwordConfirm: '', name: '', email: '', website: '' })
const loading = ref(false)
const error = ref(null)
@@ -30,7 +31,7 @@ watch(
() => ui.signupOpen,
(open) => {
if (open) {
Object.assign(form, { loginId: '', password: '', passwordConfirm: '', name: '', email: '' })
Object.assign(form, { loginId: '', password: '', passwordConfirm: '', name: '', email: '', website: '' })
agree.terms = false
agree.privacy = false
openDoc.value = ''
@@ -41,6 +42,10 @@ watch(
async function handleSignup() {
error.value = null
if (!ui.signupEnabled) {
error.value = '현재 회원가입이 제한되어 있습니다.'
return
}
if (!form.loginId || !form.password || !form.name) {
error.value = '아이디, 비밀번호, 이름은 필수입니다.'
return
@@ -64,6 +69,7 @@ async function handleSignup() {
password: form.password,
name: form.name,
email: form.email || null,
website: form.website, // 허니팟(정상 사용자는 빈 값)
})
alert('회원가입이 완료되었습니다. 로그인해 주세요.')
ui.openLogin() // 회원가입 닫고 로그인 팝업으로 전환
@@ -89,12 +95,20 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown))
<button class="close" type="button" aria-label="닫기" @click="ui.closeSignup()">×</button>
<h2>회원가입</h2>
<form class="form" @submit.prevent="handleSignup">
<!-- 회원가입 제한 : 대신 안내 -->
<div v-if="!ui.signupEnabled" class="signup-blocked">
<p class="blk-title">🔒 현재 회원가입이 제한되어 있습니다.</p>
<p class="blk-desc">관리자 설정에 따라 신규 회원가입을 받지 않고 있습니다.</p>
</div>
<form v-else class="form" @submit.prevent="handleSignup">
<input v-model="form.loginId" type="text" placeholder="아이디 (4~50자)" autocomplete="username" :disabled="loading" />
<input v-model="form.password" type="password" placeholder="비밀번호 (8자 이상)" autocomplete="new-password" :disabled="loading" />
<input v-model="form.passwordConfirm" type="password" placeholder="비밀번호 확인" autocomplete="new-password" :disabled="loading" />
<input v-model="form.name" type="text" placeholder="이름" :disabled="loading" />
<input v-model="form.email" type="email" placeholder="이메일 (선택)" autocomplete="email" :disabled="loading" />
<!-- 허니팟: 사람에겐 숨김, 봇이 채우면 차단 -->
<input v-model="form.website" type="text" name="website" class="hp-field" tabindex="-1" autocomplete="off" aria-hidden="true" />
<!-- 약관 동의 -->
<div class="agree">
@@ -266,6 +280,29 @@ h2 {
white-space: pre-wrap;
word-break: break-word;
}
/* 허니팟: 화면 밖으로 숨김(스크린리더/봇 인식 위해 display:none 대신 off-screen) */
.hp-field {
position: absolute !important;
left: -9999px;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.signup-blocked {
text-align: center;
padding: 1.5rem 0.5rem 0.5rem;
}
.blk-title {
font-size: 1rem;
font-weight: 700;
color: #c0392b;
}
.blk-desc {
margin-top: 0.5rem;
font-size: 0.85rem;
opacity: 0.75;
}
.links {
margin-top: 1.25rem;
text-align: center;