223 lines
8.9 KiB
PHP
223 lines
8.9 KiB
PHP
@extends('web.layouts.auth')
|
|
|
|
@section('title', '로그인 | PIN FOR YOU')
|
|
@section('meta_description', 'PIN FOR YOU 로그인 페이지입니다.')
|
|
@section('canonical', url('/auth/login'))
|
|
|
|
@section('h1', '로그인')
|
|
@section('desc', '안전한 거래를 위해 로그인해 주세요.')
|
|
@section('headline', '안전한 핀 발송/거래')
|
|
@section('subheadline', '로그인 후 구매/문의 내역을 빠르게 확인할 수 있어요.')
|
|
@section('card_aria', '로그인 폼')
|
|
|
|
{{-- ✅ reCAPTCHA 스크립트/공통함수는 이 페이지에서만 로드 --}}
|
|
@push('recaptcha')
|
|
<script>window.__recaptchaSiteKey = @json(config('services.recaptcha.site_key'));</script>
|
|
<script src="https://www.google.com/recaptcha/api.js?render={{ config('services.recaptcha.site_key') }}"></script>
|
|
<script src="{{ asset('assets/js/recaptcha-v3.js') }}"></script>
|
|
@endpush
|
|
|
|
@section('auth_content')
|
|
<form class="auth-form" method="post" action="{{ route('web.auth.login.prc') }}" id="loginForm">
|
|
@csrf
|
|
|
|
<input type="hidden" name="return_url" value="{{ request('return_url', '/') }}">
|
|
<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response" value="">
|
|
|
|
<img
|
|
class="reg-step0-hero__img"
|
|
src="{{ asset('assets/images/web/member/login.webp') }}"
|
|
alt=""
|
|
loading="lazy"
|
|
onerror="this.style.display='none';"
|
|
/>
|
|
|
|
<div class="auth-field">
|
|
<label class="auth-label" for="login_id">아이디(이메일)</label>
|
|
<input class="auth-input" id="login_id" name="mem_email" type="email"
|
|
placeholder="example@domain.com" autocomplete="username"
|
|
value="{{ old('mem_email') }}">
|
|
</div>
|
|
|
|
<div class="auth-field">
|
|
<label class="auth-label" for="login_pw">비밀번호</label>
|
|
<input class="auth-input" id="login_pw" name="mem_pw" type="password"
|
|
placeholder="비밀번호" autocomplete="current-password">
|
|
</div>
|
|
<div class="auth-row">
|
|
<label class="auth-check">
|
|
{{-- <input type="checkbox" name="auto_login" value="1">--}}
|
|
{{-- 자동 로그인--}}
|
|
</label>
|
|
<div class="auth-links-inline">
|
|
<a class="auth-link" href="{{ route('web.auth.find_id') }}">아이디 찾기</a>
|
|
<span class="auth-dot">·</span>
|
|
<a class="auth-link" href="{{ route('web.auth.find_password.show') }}">비밀번호 찾기</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="auth-actions">
|
|
<button class="auth-btn auth-btn--primary" type="submit">로그인</button>
|
|
<a class="auth-btn auth-btn--ghost" href="{{ route('web.auth.register') }}">회원가입</a>
|
|
</div>
|
|
</form>
|
|
@endsection
|
|
|
|
@section('auth_bottom')
|
|
<div class="auth-links">
|
|
<a class="auth-link" href="{{ route('web.cs.faq.index') }}">FAQ</a>
|
|
<span class="auth-dot">·</span>
|
|
<a class="auth-link" href="{{ route('web.cs.qna.index') }}">1:1 문의</a>
|
|
<span class="auth-dot">·</span>
|
|
<a class="auth-link" href="{{ route('web.cs.kakao.index') }}">카카오 상담</a>
|
|
</div>
|
|
@endsection
|
|
|
|
|
|
@push('scripts')
|
|
<script>
|
|
(function(){
|
|
const form = document.getElementById('loginForm');
|
|
if (!form) return;
|
|
|
|
// ✅ 너 템플릿이 id를 뭘 쓰든 대응 (id 우선, 없으면 name으로 fallback)
|
|
const emailEl =
|
|
document.getElementById('login_id')
|
|
|| form.querySelector('input[name="mem_email"]')
|
|
|| form.querySelector('input[type="email"]');
|
|
|
|
const pwEl =
|
|
document.getElementById('login_pw')
|
|
|| form.querySelector('input[name="mem_pw"]')
|
|
|| form.querySelector('input[type="password"]');
|
|
|
|
const btn = form.querySelector('button[type="submit"]');
|
|
|
|
function isEmail(v){
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
|
|
}
|
|
|
|
function ensureHiddenRecaptcha(){
|
|
let el = document.getElementById('g-recaptcha-response');
|
|
if(!el){
|
|
el = document.createElement('input');
|
|
el.type = 'hidden';
|
|
el.id = 'g-recaptcha-response';
|
|
el.name = 'g-recaptcha-response';
|
|
form.appendChild(el);
|
|
}
|
|
return el;
|
|
}
|
|
|
|
function ensureReturnUrl(){
|
|
let el = form.querySelector('input[name="return_url"]');
|
|
if(!el){
|
|
el = document.createElement('input');
|
|
el.type = 'hidden';
|
|
el.name = 'return_url';
|
|
el.value = '/';
|
|
form.appendChild(el);
|
|
} else if (!el.value) {
|
|
el.value = '/';
|
|
}
|
|
return el;
|
|
}
|
|
|
|
async function getRecaptchaToken(action){
|
|
// 1) 프로젝트 공통 함수가 있으면 우선
|
|
if (typeof window.recaptchaV3Exec === 'function') {
|
|
try {
|
|
const t = await window.recaptchaV3Exec(action);
|
|
return t || '';
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// 2) fallback: grecaptcha.execute
|
|
const siteKey = window.__recaptchaSiteKey || '';
|
|
if (!siteKey) return '';
|
|
if (typeof window.grecaptcha === 'undefined') return '';
|
|
|
|
try {
|
|
await new Promise(r => window.grecaptcha.ready(r));
|
|
const t = await window.grecaptcha.execute(siteKey, { action });
|
|
return t || '';
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
form.addEventListener('submit', async function (e) {
|
|
e.preventDefault();
|
|
|
|
if (typeof clearMsg === 'function') clearMsg();
|
|
|
|
const email = (emailEl?.value || '').trim();
|
|
const pw = (pwEl?.value || '');
|
|
|
|
if (!email) {
|
|
await alertMsg('아이디(이메일)를 입력해 주세요.');
|
|
await showMsg("아이디(이메일)를 입력해 주세요.", { type: 'alert', title: '폼체크' });
|
|
emailEl?.focus();
|
|
return;
|
|
}
|
|
if (!isEmail(email)) {
|
|
await showMsg("아이디는 이메일 형식이어야 합니다.", { type: 'alert', title: '폼체크' });
|
|
emailEl?.focus();
|
|
return;
|
|
}
|
|
if (!pw) {
|
|
await showMsg("비밀번호를 입력해 주세요.", { type: 'alert', title: '폼체크' });
|
|
pwEl?.focus();
|
|
return;
|
|
}
|
|
|
|
// return_url 없으면 기본 세팅
|
|
ensureReturnUrl();
|
|
|
|
// 버튼 잠금
|
|
if (btn) btn.disabled = true;
|
|
|
|
try {
|
|
// ✅ 운영에서만 recaptcha 토큰 넣기 (서버도 동일 정책)
|
|
const isProd = @json(app()->environment('production'));
|
|
const hasKey = @json((bool) config('services.recaptcha.site_key'));
|
|
|
|
if (isProd && hasKey) {
|
|
const hidden = ensureHiddenRecaptcha();
|
|
hidden.value = ''; // 초기화
|
|
|
|
const token = await getRecaptchaToken('login');
|
|
hidden.value = token || '';
|
|
|
|
// ✅ 토큰이 비면 submit 막아야 서버 required 안 터짐
|
|
if (!hidden.value) {
|
|
if (btn) btn.disabled = false;
|
|
await showMsg("보안 검증(reCAPTCHA) 토큰 생성에 실패했습니다. 새로고침 후 다시 시도해 주세요.", { type: 'alert', title: '보안검증 실패' });
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ✅ 실제 전송
|
|
form.submit();
|
|
|
|
} catch (err) {
|
|
if (btn) btn.disabled = false;
|
|
await showMsg("로그인 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.", { type: 'alert', title: '오류' });
|
|
}
|
|
});
|
|
|
|
// 서버에서 내려온 로그인 실패 메시지 표시
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
const msg = @json($errors->first('login'));
|
|
if (msg) {
|
|
await showMsg(msg, { type: 'alert', title: '로그인 실패' });
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
@endpush
|
|
|
|
|