버그 수정, 모임장 설정 수정

This commit is contained in:
2025-05-31 08:13:36 +09:00
parent 27fbf0541d
commit e609a8ce52
9 changed files with 314 additions and 105 deletions
+113 -61
View File
@@ -188,6 +188,36 @@ exports.createClub = async (req, res) => {
console.log(`${ownerEmail}에게 초대 이메일 발송 예정`); console.log(`${ownerEmail}에게 초대 이메일 발송 예정`);
} }
// 베이직 1개월 플랜 자동 추가
try {
// 베이직 플랜 조회 (ID가 1이라고 가정, 실제 환경에 맞게 조정 필요)
const basicPlan = await require('../models').SubscriptionPlan.findOne({
where: { name: 'Basic' }
});
if (basicPlan) {
const startDate = new Date();
const endDate = new Date();
endDate.setMonth(endDate.getMonth() + 1); // 1개월 추가
await require('../models').ClubSubscription.create({
clubId: newClub.id,
planId: basicPlan.id,
startDate,
endDate,
status: 'active',
price: basicPlan.price || 0
});
console.log(`클럽 ${newClub.id}에 베이직 1개월 플랜 자동 추가됨`);
} else {
console.warn('베이직 플랜을 찾을 수 없어 자동 구독이 추가되지 않았습니다.');
}
} catch (subscriptionError) {
console.error('자동 구독 추가 오류:', subscriptionError);
// 구독 추가 실패해도 클럽 생성은 성공으로 처리
}
res.status(201).json(newClub); res.status(201).json(newClub);
} catch (error) { } catch (error) {
console.error('클럽 생성 오류:', error); console.error('클럽 생성 오류:', error);
@@ -198,18 +228,24 @@ exports.createClub = async (req, res) => {
// 클럽 정보 업데이트 // 클럽 정보 업데이트
exports.updateClub = async (req, res) => { exports.updateClub = async (req, res) => {
try { try {
const { clubId, name, location, description, contact, ownerEmail, status, femaleHandicap } = req.body; const { clubId, name, location, description, contact, ownerEmail, status, femaleHandicap, ownerId } = req.body;
const club = await Club.findByPk(clubId); const club = await Club.findByPk(clubId);
if (!club) { if (!club) {
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' }); return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
} }
// 이메일이 변경된 경우 새 모임장 확인 // 모임장 변경 확인 (직접 ownerId를 전달받거나 이메일로 변경)
let newOwnerId = club.ownerId; let newOwnerId = club.ownerId;
let ownerChanged = false; let ownerChanged = false;
if (ownerEmail && ownerEmail !== club.ownerEmail) { // ownerId가 직접 전달된 경우 (클럽 설정에서 모임장 선택)
if (ownerId && ownerId !== club.ownerId) {
newOwnerId = ownerId;
ownerChanged = true;
}
// 이메일을 통한 모임장 변경 (기존 기능 유지)
else if (ownerEmail && ownerEmail !== club.ownerEmail) {
const newOwner = await User.findOne({ where: { email: ownerEmail } }); const newOwner = await User.findOne({ where: { email: ownerEmail } });
if (!newOwner) { if (!newOwner) {
return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' }); return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' });
@@ -229,48 +265,13 @@ exports.updateClub = async (req, res) => {
ownerId: newOwnerId ownerId: newOwnerId
}); });
// 모임장이 변경된 경우 Members 테이블 업데이트 // 모임장이 변경된 경우 모임장 변경 메소드 호출
if (ownerChanged) { if (ownerChanged) {
// 기존 모임장을 정회원으로 변경 try {
await Member.update( // 모임장 변경 내부 메소드 호출
{ memberType: '정회원' }, await changeClubOwnerInternal(clubId, newOwnerId, ownerEmail);
{ } catch (error) {
where: { throw error;
clubId: clubId,
userId: club.ownerId,
memberType: '모임장'
}
}
);
// 새 모임장 확인 및 업데이트
const newOwnerMember = await Member.findOne({
where: {
clubId: clubId,
userId: newOwnerId
}
});
if (newOwnerMember) {
// 기존 회원인 경우 모임장으로 변경
await newOwnerMember.update({ memberType: '모임장' });
} else {
// 새로운 사용자 정보 가져오기
const newOwnerUser = await User.findByPk(newOwnerId);
if (!newOwnerUser) {
return res.status(404).json({ message: '새 모임장 사용자 정보를 찾을 수 없습니다.' });
}
// 새 회원으로 모임장 추가
await Member.create({
userId: newOwnerId,
clubId: clubId,
name: newOwnerUser.name || ownerEmail.split('@')[0], // 이름이 없는 경우 이메일 아이디 사용
gender: '남성', // 성별 정보가 없는 경우 기본값
memberType: '모임장',
joinDate: new Date(),
status: 'active'
});
} }
} }
@@ -362,6 +363,7 @@ exports.getClubMembers = async (req, res) => {
const memberData = member.toJSON(); const memberData = member.toJSON();
return { return {
id: memberData.id, id: memberData.id,
userId: memberData.userId || null,
name: memberData.name, name: memberData.name,
gender: memberData.gender, gender: memberData.gender,
memberType: memberData.memberType, memberType: memberData.memberType,
@@ -437,28 +439,17 @@ exports.updateClubMember = async (req, res) => {
if (!member) { if (!member) {
return res.status(404).json({ message: '회원을 찾을 수 없습니다.' }); return res.status(404).json({ message: '회원을 찾을 수 없습니다.' });
} }
// 모임장은 한 명만 존재할 수 있음 // 모임장으로 변경 금지 - 모임장 변경은 별도 API로 처리
if (memberType === '모임장') { if (memberType === '모임장' && member.memberType !== '모임장') {
const existingOwner = await Member.findOne({ return res.status(403).json({ message: '모임장 변경은 클럽 설정에서만 가능합니다.' });
where: {
clubId: clubId,
memberType: '모임장',
status: 'active',
id: { [Op.ne]: memberId }
}
});
if (existingOwner) {
return res.status(400).json({ message: '이미 다른 모임장이 존재합니다.' });
}
} }
// 회원 정보 업데이트 // 회원 정보 업데이트 (모임장 유지 또는 다른 유형으로 변경만 허용)
await member.update({ await member.update({
name: name || member.name, name: name || member.name,
gender: gender || member.gender, gender: gender || member.gender,
memberType: memberType || member.memberType, memberType: (member.memberType === '모임장' && memberType !== '모임장') ? '모임장' : memberType || member.memberType,
email: email || member.email, email: email || member.email,
phone: phone || member.phone, phone: phone || member.phone,
handicap: handicap || member.handicap, handicap: handicap || member.handicap,
@@ -479,6 +470,67 @@ exports.updateClubMember = async (req, res) => {
} }
}; };
// 모임장 변경 내부 메소드
const changeClubOwnerInternal = async (clubId, newOwnerId, ownerEmail = null) => {
// 트랜잭션 시작
const t = await sequelize.transaction();
try {
// 기존 모임장을 운영진으로 변경
await Member.update(
{ memberType: '운영진' },
{
where: {
clubId: clubId,
memberType: '모임장',
status: 'active'
},
transaction: t
}
);
// 새 모임장 확인 및 업데이트
const newOwnerMember = await Member.findOne({
where: {
clubId: clubId,
userId: newOwnerId,
status: 'active'
}
});
if (newOwnerMember) {
// 기존 회원인 경우 모임장으로 변경
await newOwnerMember.update({ memberType: '모임장' }, { transaction: t });
} else {
// 새로운 사용자 정보 가져오기
const newOwnerUser = await User.findByPk(newOwnerId);
if (!newOwnerUser) {
await t.rollback();
throw new Error('새 모임장 사용자 정보를 찾을 수 없습니다.');
}
// 새 회원으로 모임장 추가
await Member.create({
userId: newOwnerId,
clubId: clubId,
name: newOwnerUser.name || (ownerEmail ? ownerEmail.split('@')[0] : ''), // 이름이 없는 경우 이메일 아이디 사용
gender: '남성', // 성별 정보가 없는 경우 기본값
memberType: '모임장',
joinDate: new Date(),
status: 'active'
}, { transaction: t });
}
// 트랜잭션 커밋
await t.commit();
return true;
} catch (error) {
// 오류 발생 시 트랜잭션 롤백
await t.rollback();
throw error;
}
};
// 클럽 회원 삭제 // 클럽 회원 삭제
exports.deleteClubMember = async (req, res) => { exports.deleteClubMember = async (req, res) => {
try { try {
+12 -3
View File
@@ -461,14 +461,23 @@ exports.requestPayment = async (req, res) => {
planChange: planChange || false planChange: planChange || false
}); });
// 플랜 정보 조회 (planId가 있는 경우)
let planName = '';
if (planId) {
const plan = await SubscriptionPlan.findByPk(planId);
if (plan) {
planName = plan.name; // 베이직 또는 프리미엄 등의 플랜 이름
}
}
// 토스페이먼츠에 전송할 데이터 구성 // 토스페이먼츠에 전송할 데이터 구성
const paymentData = { const paymentData = {
orderId: paymentInfo.orderId, orderId: paymentInfo.orderId,
amount: totalAmount, amount: totalAmount,
orderName: planId ? `구독 플랜: ${planDuration}개월` : '추가 기능 구매', orderName: planId ? `${planName} 플랜: ${planDuration}개월` : '추가 기능 구매',
customerName: club.name, customerName: club.name,
successUrl: successUrl || `${process.env.FRONTEND_URL}/payment/success`, successUrl: successUrl || `${req.headers.origin}/payment/success`,
failUrl: failUrl || `${process.env.FRONTEND_URL}/payment/fail`, failUrl: failUrl || `${req.headers.origin}/payment/fail`,
customerEmail: req.user.email customerEmail: req.user.email
}; };
+14 -1
View File
@@ -15,7 +15,7 @@
<div class="club-header"> <div class="club-header">
<div class="club-title"> <div class="club-title">
<h3>{{ selectedClub.name }}</h3> <h3>{{ selectedClub.name }}</h3>
<router-link v-if="userRole === 'admin' || userRole === '모임장' || userRole === '운영진'" <router-link v-if="userRole === 'admin' || userClubRole === '모임장'"
:to="{ name: 'ClubSettings' }" :to="{ name: 'ClubSettings' }"
class="settings-icon" class="settings-icon"
title="클럽 설정"> title="클럽 설정">
@@ -340,6 +340,19 @@ const loadMenuItems = async () => {
} }
}; };
// 사이드바 새로고침 메소드
const refreshSidebar = async () => {
console.log('사이드바 새로고침 시작...');
await fetchClubs();
await fetchClubInfo();
loadMenuItems();
initializeExpandedGroups(); // 메뉴 그룹 초기화
console.log('사이드바 새로고침 완료');
};
// 사이드바 새로고침 메소드를 전역으로 노출
provide('refreshSidebar', refreshSidebar);
// 사용자 정보 로드 // 사용자 정보 로드
const loadUserData = async () => { const loadUserData = async () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
+18 -3
View File
@@ -47,6 +47,13 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 클럽 추가 버튼 -->
<div class="club-add-wrapper">
<Button icon="pi pi-plus-circle" class="club-add-button p-button-outlined" @click="goToClubCreate"
title="새 클럽 만들기" />
</div>
<!-- 사용자 메뉴 --> <!-- 사용자 메뉴 -->
<div class="user-menu-wrapper"> <div class="user-menu-wrapper">
<Button @click="toggleUserMenu" class="user-menu-button p-button-outlined"> <Button @click="toggleUserMenu" class="user-menu-button p-button-outlined">
@@ -301,6 +308,11 @@ const toggleNotifications = (event) => {
event.stopPropagation(); event.stopPropagation();
}; };
// 클럽 생성 페이지로 이동
const goToClubCreate = () => {
router.push({ name: 'UserClubCreate' });
};
// 문서 클릭 시 알림 패널 닫기 // 문서 클릭 시 알림 패널 닫기
const handleDocumentClick = (event) => { const handleDocumentClick = (event) => {
const notificationWrapper = document.querySelector('.notification-wrapper'); const notificationWrapper = document.querySelector('.notification-wrapper');
@@ -348,13 +360,15 @@ onBeforeUnmount(() => {
.user-menu-wrapper, .user-menu-wrapper,
.admin-menu-wrapper, .admin-menu-wrapper,
.notification-wrapper { .notification-wrapper,
.club-add-wrapper {
position: relative; position: relative;
} }
.user-menu-button, .user-menu-button,
.admin-menu-button, .admin-menu-button,
.notification-button { .notification-button,
.club-add-button {
background-color: transparent !important; background-color: transparent !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important; border: 1px solid rgba(255, 255, 255, 0.3) !important;
color: white !important; color: white !important;
@@ -362,7 +376,8 @@ onBeforeUnmount(() => {
.user-menu-button:hover, .user-menu-button:hover,
.admin-menu-button:hover, .admin-menu-button:hover,
.notification-button:hover { .notification-button:hover,
.club-add-button:hover {
background-color: rgba(255, 255, 255, 0.1) !important; background-color: rgba(255, 255, 255, 0.1) !important;
} }
+3 -1
View File
@@ -85,7 +85,7 @@ const clubService = {
...memberData ...memberData
}); });
}, },
/** /**
* 클럽 회원 삭제 * 클럽 회원 삭제
* @param {Number} clubId - 클럽 ID * @param {Number} clubId - 클럽 ID
@@ -95,6 +95,8 @@ const clubService = {
deleteClubMember(clubId, memberId) { deleteClubMember(clubId, memberId) {
return apiClient.post('/api/club/members/delete', { clubId, memberId }); return apiClient.post('/api/club/members/delete', { clubId, memberId });
}, },
/** /**
* 클럽 회원 통계 조회 * 클럽 회원 통계 조회
+12 -1
View File
@@ -34,7 +34,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted, inject } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast'; import { useToast } from 'primevue/usetoast';
import clubService from '@/services/clubService'; import clubService from '@/services/clubService';
@@ -44,6 +44,7 @@ import axios from 'axios'
const toast = useToast(); const toast = useToast();
const authStore = useAuthStore(); const authStore = useAuthStore();
const router = useRouter(); const router = useRouter();
const refreshSidebar = inject('refreshSidebar'); // App.vue에서 제공하는 사이드바 새로고침 메소드 주입
const clubName = ref(''); const clubName = ref('');
const clubDescription = ref(''); const clubDescription = ref('');
@@ -106,6 +107,16 @@ const handleCreateClub = async () => {
// 성공 메시지 // 성공 메시지
toast.add({ severity: 'success', summary: '성공', detail: '클럽이 성공적으로 생성되었습니다.', life: 3000 }); toast.add({ severity: 'success', summary: '성공', detail: '클럽이 성공적으로 생성되었습니다.', life: 3000 });
// 사이드바 새로고침
if (refreshSidebar) {
try {
await refreshSidebar();
console.log('클럽 생성 후 사이드바 새로고침 완료');
} catch (refreshError) {
console.error('사이드바 새로고침 오류:', refreshError);
}
}
// 클럽 페이지로 이동 // 클럽 페이지로 이동
router.push('/club'); router.push('/club');
} catch (error) { } catch (error) {
+90 -21
View File
@@ -27,6 +27,15 @@
<input type="number" id="femaleHandicap" v-model="clubInfo.femaleHandicap" class="form-control" min="0" max="100" /> <input type="number" id="femaleHandicap" v-model="clubInfo.femaleHandicap" class="form-control" min="0" max="100" />
<small class="form-text text-muted">여성 회원을 멤버(게스트) 등록할 기본 핸디캡 값입니다.</small> <small class="form-text text-muted">여성 회원을 멤버(게스트) 등록할 기본 핸디캡 값입니다.</small>
</div> </div>
<div class="form-group">
<label for="clubOwner">모임장 설정</label>
<select id="clubOwner" v-model="selectedOwnerId" class="form-control">
<option v-for="member in members" :key="member.id" :value="member.userId">
{{ member.name }} ({{ member.memberType }}) [ID: {{ member.userId }}]
</option>
</select>
<small class="form-text text-muted">모임장 권한을 이전할 회원을 선택합니다. 모임장은 클럽의 모든 관리 권한을 가집니다.</small>
</div>
<div class="form-actions"> <div class="form-actions">
<button @click="saveClubInfo" class="btn btn-primary">저장</button> <button @click="saveClubInfo" class="btn btn-primary">저장</button>
</div> </div>
@@ -111,7 +120,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed, inject } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast'; import { useToast } from 'primevue/usetoast';
import { useClubStore } from '@/stores/clubStore'; import { useClubStore } from '@/stores/clubStore';
@@ -121,6 +130,7 @@ const clubStore = useClubStore();
const router = useRouter(); const router = useRouter();
const toast = useToast(); const toast = useToast();
const refreshSidebar = inject('refreshSidebar'); // 사이드바 새로고침 메소드 주입
// 클럽 정보 // 클럽 정보
const clubInfo = ref({ const clubInfo = ref({
@@ -128,15 +138,21 @@ const clubInfo = ref({
name: '', name: '',
description: '', description: '',
location: '', location: '',
femaleHandicap: 15 // 기본값 설정 femaleHandicap: 15, // 기본값 설정
ownerId: null // 모임장 ID
}); });
// 구독 정보 // 구독 정보
const subscriptionInfo = ref(null); const subscriptionInfo = ref(null);
const clubId = localStorage.getItem('clubId');
// 기능 목록 // 기능 정보
const features = ref([]); const features = ref([]);
// 회원 목록
const members = ref([]);
const selectedOwnerId = ref(null); // 선택된 모임장 ID
// 구독 상태 텍스트 계산 // 구독 상태 텍스트 계산
const subscriptionStatusText = computed(() => { const subscriptionStatusText = computed(() => {
if (!subscriptionInfo.value) return '없음'; if (!subscriptionInfo.value) return '없음';
@@ -179,9 +195,8 @@ const formatDate = (dateString) => {
// 클럽 정보 로드 // 클럽 정보 로드
const loadClubInfo = async () => { const loadClubInfo = async () => {
try { try {
const clubId = localStorage.getItem('clubId');
if (!clubId) { if (!clubId) {
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 찾을 수 없습니다.', life: 3000 }); router.push('/club');
return; return;
} }
@@ -192,17 +207,19 @@ const loadClubInfo = async () => {
description: clubData.description || '', description: clubData.description || '',
location: clubData.location || '', location: clubData.location || '',
memberCount: clubData.memberCount || 0, memberCount: clubData.memberCount || 0,
femaleHandicap: clubData.femaleHandicap || 15 femaleHandicap: clubData.femaleHandicap || 15,
ownerId: clubData.ownerId // 모임장 ID 추가
}; };
// 구독 정보 로드
await loadSubscriptionInfo(clubId);
// 기능 정보 로드 // 선택된 모임장 ID 설정
selectedOwnerId.value = clubData.ownerId;
// 구독 정보 및 기능 정보 로드
await loadSubscriptionInfo(clubId);
await loadFeatures(clubId); await loadFeatures(clubId);
} catch (error) { } catch (error) {
console.error('클럽 정보 로드 오류:', error); console.error('클럽 정보 로드 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 불러오는 중 오류가 발생했습니다.', life: 3000 }); toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 가져오는 중 오류가 발생했습니다.', life: 3000 });
} }
}; };
@@ -283,34 +300,86 @@ const getFeatureIcon = (featureType) => {
// 클럽 정보 저장 // 클럽 정보 저장
const saveClubInfo = async () => { const saveClubInfo = async () => {
try { try {
const clubId = clubInfo.value.id;
if (!clubId) { if (!clubId) {
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 찾을 수 없습니다.', life: 3000 }); router.push('/club');
return; return;
} }
// clubService를 사용하여 클럽 정보 업데이트 // 모임장 변경 확인
await clubService.updateClub(clubId, { const isOwnerChanged = selectedOwnerId.value !== clubInfo.value.ownerId;
let updateData = {
name: clubInfo.value.name, name: clubInfo.value.name,
description: clubInfo.value.description, description: clubInfo.value.description,
location: clubInfo.value.location, location: clubInfo.value.location,
femaleHandicap: clubInfo.value.femaleHandicap femaleHandicap: clubInfo.value.femaleHandicap
}); };
// 모임장이 변경되었다면 확인 메시지 표시
if (isOwnerChanged) {
const confirmChange = confirm('모임장을 변경하시겠습니까? 모임장 권한이 새로운 회원에게 이전됩니다.');
if (!confirmChange) {
// 선택을 원래 모임장으로 되돌림
selectedOwnerId.value = clubInfo.value.ownerId;
return;
}
// 모임장 ID 추가
updateData.ownerId = selectedOwnerId.value;
}
// clubService를 사용하여 클럽 정보 업데이트
await clubService.updateClub(clubId, updateData);
// 성공 메시지 표시 // 성공 메시지 표시
toast.add({ severity: 'success', summary: '성공', detail: '클럽 정보가 저장되었습니다.', life: 3000 }); let successMessage = '클럽 정보가 저장되었습니다.';
if (isOwnerChanged) {
successMessage = '클럽 정보가 저장되었으며, 모임장이 변경되었습니다.';
}
// 클럽 정보 다시 불러오기 toast.add({ severity: 'success', summary: '성공', detail: successMessage, life: 3000 });
// 클럽 정보와 회원 목록 다시 불러오기
await loadClubInfo(); await loadClubInfo();
await loadMembers();
// 사이드바 새로고침
if (refreshSidebar) {
try {
await refreshSidebar();
} catch (refreshError) {
console.error('사이드바 새로고침 오류:', refreshError);
}
}
} catch (error) { } catch (error) {
console.error('클럽 정보 저장 오류:', error); console.error('클럽 정보 저장 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 저장하는 중 오류가 발생했습니다.', life: 3000 }); toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 저장하는 중 오류가 발생했습니다.', life: 3000 });
} }
}; };
// 회원 목록 가져오기
const loadMembers = async () => {
try {
const response = await clubService.getClubMembers(clubId);
if (response.data) {
const data = response.data;
members.value = data;
// 현재 모임장 정보 설정
if (clubInfo.value && clubInfo.value.ownerId) {
selectedOwnerId.value = clubInfo.value.ownerId;
}
}
} catch (error) {
console.error('회원 목록 가져오기 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '회원 목록을 가져오는 중 오류가 발생했습니다.', life: 3000 });
}
};
// 컴포넌트 마운트 시 데이터 로드 // 컴포넌트 마운트 시 데이터 로드
onMounted(() => { onMounted(async () => {
loadClubInfo(); await loadClubInfo();
await loadMembers();
}); });
</script> </script>
+5 -5
View File
@@ -85,7 +85,7 @@
</div> </div>
<div class="field col-6 mb-3"> <div class="field col-6 mb-3">
<label for="memberType" class="mb-1">회원 유형</label> <label for="memberType" class="mb-1">회원 유형</label>
<Select id="memberType" v-model="member.memberType" :options="memberTypes" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" /> <Select id="memberType" v-model="member.memberType" :options="memberTypeOptions" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" />
</div> </div>
<div class="field col-6 mb-3"> <div class="field col-6 mb-3">
<label for="phone" class="mb-1">전화번호</label> <label for="phone" class="mb-1">전화번호</label>
@@ -194,12 +194,12 @@ import { useToast } from 'primevue/usetoast';
const clubStore = useClubStore(); const clubStore = useClubStore();
const toast = useToast(); const toast = useToast();
// 회원 유형 목록 // 회원 유형 옵션
const memberTypes = ref([ const memberTypeOptions = ref([
{ name: '정회원', code: '정회원' }, { name: '정회원', code: '정회원' },
{ name: '준회원', code: '준회원' }, { name: '준회원', code: '준회원' },
{ name: '운영진', code: '운영진' }, { name: '운영진', code: '운영진' }
{ name: '모임장', code: '모임장' } // 모임장 옵션은 제거 - 클럽 설정에서만 변경 가능하도록 제한
]); ]);
// 성별 옵션 // 성별 옵션
@@ -1097,6 +1097,10 @@ const processPayment = async () => {
// 토스페이먼츠 SDK 초기화 // 토스페이먼츠 SDK 초기화
const tossPayments = await loadTossPaymentsSDK(); const tossPayments = await loadTossPaymentsSDK();
// 결제 성공/실패 핸들러 등록 (전역 함수로 등록)
window.handlePaymentSuccess = handlePaymentSuccess;
window.handlePaymentFail = handlePaymentFail;
// 결제 창 열기 - 결제 방법을 미리 지정하지 않고 토스페이먼츠 결제 창에서 선택하도록 함 // 결제 창 열기 - 결제 방법을 미리 지정하지 않고 토스페이먼츠 결제 창에서 선택하도록 함
await tossPayments.requestPayment({ await tossPayments.requestPayment({
...response.data.paymentData, ...response.data.paymentData,
@@ -1118,6 +1122,21 @@ const processPayment = async () => {
} }
}; };
// 선택 항목 초기화
const resetSelections = () => {
selectedPlan.value = null;
selectedFeatures.value = [];
selectedPlanDuration.value = 1;
showPlans.value = false;
showFeatures.value = false;
isSelectingPlan.value = false;
isSelectingFeatures.value = false;
changeOption.value = 'immediate';
paymentAgreement.value = false;
showPaymentSection.value = false;
showAdditionalFeatures.value = false;
};
// 토스페이먼츠 SDK 로드 // 토스페이먼츠 SDK 로드
const loadTossPaymentsSDK = () => { const loadTossPaymentsSDK = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -1153,6 +1172,8 @@ const loadTossPaymentsSDK = () => {
// 결제 성공 처리 // 결제 성공 처리
const handlePaymentSuccess = async (orderId) => { const handlePaymentSuccess = async (orderId) => {
try { try {
console.log('결제 성공:', orderId);
// 데이터 다시 로드 // 데이터 다시 로드
await Promise.all([ await Promise.all([
loadCurrentSubscription(), loadCurrentSubscription(),
@@ -1162,23 +1183,40 @@ const handlePaymentSuccess = async (orderId) => {
]); ]);
// 상태 초기화 // 상태 초기화
selectedPlan.value = null; resetSelections();
selectedFeatures.value = [];
paymentAgreement.value = false;
showPaymentSection.value = false;
showPlans.value = false;
showAdditionalFeatures.value = false;
changeOption.value = isUpgrade.value ? 'immediate' : 'nextBilling';
alert('결제가 성공적으로 완료되었습니다.'); // 성공 메시지 표시 (토스트 메시지 추가)
toast.add({
severity: 'success',
summary: '결제 성공',
detail: '구독 플랜 결제가 성공적으로 완료되었습니다.',
life: 5000
});
} catch (error) { } catch (error) {
console.error('결제 성공 후 처리 오류:', error); console.error('결제 성공 후 처리 오류:', error);
toast.add({
severity: 'error',
summary: '오류',
detail: '구독 정보를 새로고침하는 중 오류가 발생했습니다.',
life: 5000
});
} }
}; };
// 결제 실패 처리 // 결제 실패 처리
const handlePaymentFail = (message) => { const handlePaymentFail = (message) => {
alert(`결제 실패: ${message || '사용자가 결제를 취소했습니다.'}`); console.log('결제 실패:', message);
// 실패 메시지 표시 (토스트 메시지 추가)
toast.add({
severity: 'error',
summary: '결제 실패',
detail: message || '결제 처리 중 오류가 발생했습니다.',
life: 5000
});
// 결제 실패 후 초기화
resetSelections();
}; };
// 플랜 구독 (기존 기능 - 레거시) // 플랜 구독 (기존 기능 - 레거시)