## 홈페이지 (프론트엔드) - MemberLogin.jsx: 회원가입/로그인 통합 페이지 + 카카오·네이버·구글 SNS 버튼 - MemberAuth.css: 인증 페이지 공통 스타일 - hooks/useMemberAuth.jsx: 회원 인증 상태 훅 + MemberOnly 컴포넌트 (회원 전용 잠금) - Header.jsx: 로그인/회원가입 버튼 + 로그인 시 이름/로그아웃 표시 - Contact.jsx: 문의 상담 신청 → 회원 전용 (MemberOnly 적용) - App.jsx: /login, /register 라우트 추가 ## 관리자 (Admin) - AdminMember.jsx: 회원 목록/검색/상태변경/삭제 페이지 - AdminLayout.jsx: '회원 관리' 메뉴 추가 - App.jsx: /admin/members 라우트 추가 ## 백엔드 (Spring Boot) - Member.java: 회원 엔티티 (id/name/email/password/phone/company/role/active) - MemberRepository.java: 이메일 조회·중복확인·키워드 검색 - MemberController.java: 회원가입·이메일 중복확인·로그인·SNS 로그인·내 정보 CRUD - AdminController.java: 회원관리 API (목록/상세/상태변경/삭제) + 대시보드에 회원 수 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.3 KiB
JavaScript
84 lines
2.3 KiB
JavaScript
import { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
/**
|
|
* 회원 인증 상태 관리 훅.
|
|
* - isLoggedIn: 로그인 여부
|
|
* - member: 회원 정보
|
|
* - requireLogin(redirectPath): 비회원이면 로그인 페이지로 이동
|
|
*/
|
|
export function useMemberAuth() {
|
|
const navigate = useNavigate();
|
|
const [member, setMember] = useState(null);
|
|
const [loaded, setLoaded] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const token = localStorage.getItem('member_token');
|
|
const user = localStorage.getItem('member_user');
|
|
if (token && user) {
|
|
try { setMember(JSON.parse(user)); } catch { /* 손상된 데이터 무시 */ }
|
|
}
|
|
setLoaded(true);
|
|
}, []);
|
|
|
|
const logout = () => {
|
|
localStorage.removeItem('member_token');
|
|
localStorage.removeItem('member_user');
|
|
setMember(null);
|
|
navigate('/');
|
|
};
|
|
|
|
const requireLogin = (redirectPath = window.location.pathname) => {
|
|
if (!member) {
|
|
navigate(`/login?redirect=${encodeURIComponent(redirectPath)}`);
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
return {
|
|
member,
|
|
isLoggedIn: !!member,
|
|
loaded,
|
|
logout,
|
|
requireLogin,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 회원 전용 접근 보호 컴포넌트.
|
|
* 비로그인 시 잠금 오버레이 표시.
|
|
*
|
|
* 사용:
|
|
* <MemberOnly feature="상담 신청">
|
|
* <ConsultForm />
|
|
* </MemberOnly>
|
|
*/
|
|
export function MemberOnly({ children, feature = '이 기능' }) {
|
|
const { isLoggedIn, loaded } = useMemberAuth();
|
|
const navigate = useNavigate();
|
|
|
|
if (!loaded) return null;
|
|
|
|
if (!isLoggedIn) {
|
|
return (
|
|
<div className="member-guard" style={{ position:'relative', minHeight:120 }}>
|
|
{children}
|
|
<div className="member-guard-overlay">
|
|
<div className="member-guard-icon">🔒</div>
|
|
<div className="member-guard-text">{feature}은 회원 전용입니다</div>
|
|
<div className="member-guard-sub">로그인 후 이용하실 수 있습니다</div>
|
|
<button
|
|
onClick={() => navigate('/login')}
|
|
style={{ marginTop:8, padding:'8px 24px', background:'#1a5fd8', color:'#fff',
|
|
border:'none', borderRadius:8, cursor:'pointer', fontWeight:600, fontSize:14 }}>
|
|
로그인 / 회원가입
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return children;
|
|
}
|