| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- <template>
- <main>
- <div class="join--container">
- <div class="title--wrap">
- <h1>로그인</h1>
- <p class="mt--9">파이럿존 이용을 위해 로그인해 주세요</p>
- </div>
- <div class="login--wrap mt--25">
- <form @submit.prevent="handleLogin">
- <div class="input--wrap">
- <label for="login--id">아이디</label>
- <input
- id="login--id"
- v-model="loginId"
- type="text"
- placeholder="아이디를 입력해 주세요"
- autocomplete="username"
- >
- </div>
- <div class="input--wrap mt--18 pw--input--wrap">
- <label for="login--pw">비밀번호</label>
- <input
- id="login--pw"
- v-model="loginPw"
- :type="showPw ? 'text' : 'password'"
- placeholder="비밀번호를 입력해 주세요"
- autocomplete="current-password"
- >
- <button
- type="button"
- class="pw--toggle--btn"
- :aria-label="showPw ? '비밀번호 숨기기' : '비밀번호 표시'"
- @click="showPw = !showPw"
- >
- <img v-if="showPw" src="/img/ico--pw.svg" alt="비밀번호 표시" />
- <img v-else src="/img/ico--pw--off.svg" alt="비밀번호 숨김" />
- </button>
- </div>
- <div class="auto--login--wrap mt--18">
- <div class="auto--login">
- <label>
- <input type="checkbox" v-model="autoLogin">
- 자동 로그인
- </label>
- </div>
- <NuxtLink to="/find" class="find--pw--btn">아이디ㆍ비밀번호 찾기</NuxtLink>
- </div>
- <div class="login--btn--wrap mt--30">
- <button type="submit" :disabled="loggingIn">
- {{ loggingIn ? '로그인 중...' : '로그인' }}
- </button>
- </div>
- </form>
- <div class="social--login--wrap">
- <div class="social--login--txt mt--28"><span>또는</span></div>
- <ul class="social--login--list mt--26">
- <li><NuxtLink to="#" class="kakao">카카오톡</NuxtLink></li>
- <li><NuxtLink to="#" class="naver">네이버</NuxtLink></li>
- <li><NuxtLink to="#" class="apple">애플</NuxtLink></li>
- </ul>
- </div>
- <div class="join--btn--wrap mt--36">
- 아직 회원이 아니신가요? <NuxtLink to="/login/agree">회원가입</NuxtLink>
- </div>
- </div>
- </div>
- <AppAlertModal
- v-model="modal.show"
- :icon-type="modal.iconType"
- :title="modal.title"
- :message="modal.message"
- />
- </main>
- </template>
- <script setup>
- import { ref, reactive, onMounted } from 'vue'
- import { useRouter } from 'vue-router'
- import AppAlertModal from '~/components/AppAlertModal.vue'
- const router = useRouter()
- const { post } = useApi()
- const loginId = ref('')
- const loginPw = ref('')
- const showPw = ref(false)
- const autoLogin = ref(false)
- const loggingIn = ref(false)
- // 공통 알림 모달
- const modal = reactive({
- show: false,
- iconType: 'error',
- title: '',
- message: '',
- })
- const showAlert = (title, message, iconType = 'error') => {
- modal.title = title
- modal.message = message
- modal.iconType = iconType
- modal.show = true
- }
- // 로그인 처리
- const handleLogin = async () => {
- if (loggingIn.value) return
- if (!loginId.value.trim() || !loginPw.value) {
- showAlert('입력 확인', '아이디와 비밀번호를 모두 입력해 주세요.')
- return
- }
- loggingIn.value = true
- try {
- const { data, error } = await post('/users/login', {
- username: loginId.value.trim(),
- password: loginPw.value,
- auto_login: autoLogin.value,
- })
- if (error || !data?.success) {
- showAlert(
- '로그인에 실패했습니다',
- error?.message || data?.message || '아이디 또는 비밀번호가 일치하지 않습니다.\n다시 한번 확인해 주세요.'
- )
- return
- }
- // 토큰 + user 정보 저장
- const { token, expires_at, user } = data.data
- const storage = autoLogin.value ? localStorage : sessionStorage
- storage.setItem('user_token', token)
- storage.setItem('user_token_expires', expires_at)
- storage.setItem('user', JSON.stringify(user))
- storage.setItem('auto_login', autoLogin.value ? 'Y' : 'N')
- // 메인으로 이동
- router.push('/')
- } catch (e) {
- console.error('[Login] error:', e)
- showAlert('오류', '서버 오류가 발생했습니다.')
- } finally {
- loggingIn.value = false
- }
- }
- // 페이지 진입 시 — 이미 로그인되어 있으면 메인으로
- onMounted(() => {
- const token = localStorage.getItem('user_token') || sessionStorage.getItem('user_token')
- const expires = localStorage.getItem('user_token_expires') || sessionStorage.getItem('user_token_expires')
- if (token && expires) {
- if (new Date(expires.replace(' ', 'T')) > new Date()) {
- // 유효한 토큰 → 메인으로
- router.replace('/')
- } else {
- // 만료된 토큰 정리
- localStorage.removeItem('user_token')
- localStorage.removeItem('user_token_expires')
- localStorage.removeItem('user')
- sessionStorage.removeItem('user_token')
- sessionStorage.removeItem('user_token_expires')
- sessionStorage.removeItem('user')
- }
- }
- })
- </script>
|