zioinfo-web/frontend/src/pages/Contact.jsx
DESKTOP-TKLFCPRython c86bd72830 feat(homepage): 회원가입·로그인·SNS 로그인 + 관리자 회원관리
## 홈페이지 (프론트엔드)
- 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>
2026-05-31 11:49:21 +09:00

131 lines
5.7 KiB
JavaScript

import React, { useState } from 'react';
import axios from 'axios';
import './Contact.css';
import './MemberAuth.css';
import { MemberOnly } from '../hooks/useMemberAuth.jsx';
export default function Contact() {
const [form, setForm] = useState({
name:'', email:'', phone:'', category:'제품문의', subject:'', content:'', agreePrivacy: false
});
const [status, setStatus] = useState(null);
const [loading, setLoading] = useState(false);
const handleChange = e => {
const { name, value, type, checked } = e.target;
setForm(f => ({ ...f, [name]: type === 'checkbox' ? checked : value }));
};
const handleSubmit = async e => {
e.preventDefault();
if (!form.agreePrivacy) { setStatus({ type: 'error', msg: '개인정보 수집·이용에 동의해주세요.' }); return; }
setLoading(true);
try {
await axios.post('/api/inquiry', form);
setStatus({ type: 'success', msg: '문의가 접수되었습니다. 빠른 시일 내에 연락드리겠습니다.' });
setForm({ name:'', email:'', phone:'', category:'제품문의', subject:'', content:'', agreePrivacy: false });
} catch {
setStatus({ type: 'error', msg: '문의 접수 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' });
} finally {
setLoading(false);
}
};
return (
<MemberOnly feature="문의 상담 신청">
<main id="main-content" className="contact-page">
<div className="page-hero">
<div className="container">
<span className="section-label">Contact Us</span>
<h1 className="page-hero-title">문의하기</h1>
<p>GUARDiA ITSM 도입 문의 제품 상담을 받아드립니다.</p>
</div>
</div>
<section className="section">
<div className="container contact-grid">
{/* 연락처 정보 */}
<div className="contact-info">
<h2>연락처 정보</h2>
{[
{ icon: '📞', label: '대표전화', value: '02-000-0000' },
{ icon: '✉️', label: '이메일', value: 'info@zioinfo.co.kr' },
{ icon: '🕐', label: '운영시간', value: '평일 09:00 ~ 18:00' },
{ icon: '📍', label: '주소', value: '서울특별시' },
].map((c,i) => (
<div key={i} className="info-item">
<span className="info-icon">{c.icon}</span>
<div>
<strong>{c.label}</strong>
<p>{c.value}</p>
</div>
</div>
))}
</div>
{/* 문의 폼 */}
<form className="contact-form card" onSubmit={handleSubmit}>
<h2>온라인 문의</h2>
{status && (
<div className={`form-alert ${status.type}`}>
{status.type === 'success' ? '✅' : '❌'} {status.msg}
</div>
)}
<div className="form-row">
<div className="form-group">
<label htmlFor="name">성함 <span className="required">*</span></label>
<input id="name" name="name" type="text" required
value={form.name} onChange={handleChange} placeholder="홍길동" />
</div>
<div className="form-group">
<label htmlFor="phone">연락처</label>
<input id="phone" name="phone" type="tel"
value={form.phone} onChange={handleChange} placeholder="010-0000-0000" />
</div>
</div>
<div className="form-row">
<div className="form-group">
<label htmlFor="email">이메일 <span className="required">*</span></label>
<input id="email" name="email" type="email" required
value={form.email} onChange={handleChange} placeholder="your@email.com" />
</div>
<div className="form-group">
<label htmlFor="category">문의 유형</label>
<select id="category" name="category" value={form.category} onChange={handleChange}>
<option>제품문의</option>
<option>데모 신청</option>
<option>기술지원</option>
<option>사업제안</option>
<option>채용문의</option>
<option>기타</option>
</select>
</div>
</div>
<div className="form-group">
<label htmlFor="subject">제목 <span className="required">*</span></label>
<input id="subject" name="subject" type="text" required
value={form.subject} onChange={handleChange} placeholder="문의 제목을 입력해주세요" />
</div>
<div className="form-group">
<label htmlFor="content">문의 내용 <span className="required">*</span></label>
<textarea id="content" name="content" rows={6} required
value={form.content} onChange={handleChange}
placeholder="문의 내용을 자세히 작성해주세요." />
</div>
<label className="privacy-agree">
<input type="checkbox" name="agreePrivacy"
checked={form.agreePrivacy} onChange={handleChange} />
<span>개인정보 수집·이용에 동의합니다. <a href="/privacy" target="_blank">[보기]</a></span>
</label>
<button type="submit" className="btn btn-primary btn-lg" style={{width:'100%'}} disabled={loading}>
{loading ? '전송 중...' : '문의 접수하기'}
</button>
</form>
</div>
</section>
</main>
</MemberOnly>
);
}