버그 수정, 모임장 설정 수정
This commit is contained in:
@@ -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,
|
||||||
@@ -438,27 +440,16 @@ exports.updateClubMember = async (req, res) => {
|
|||||||
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 {
|
||||||
|
|||||||
@@ -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
@@ -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');
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ const clubService = {
|
|||||||
return apiClient.post('/api/club/members/delete', { clubId, memberId });
|
return apiClient.post('/api/club/members/delete', { clubId, memberId });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 클럽 회원 통계 조회
|
* 클럽 회원 통계 조회
|
||||||
* @param {Number} clubId - 클럽 ID
|
* @param {Number} clubId - 클럽 ID
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 추가
|
||||||
};
|
};
|
||||||
|
|
||||||
// 구독 정보 로드
|
// 선택된 모임장 ID 설정
|
||||||
await loadSubscriptionInfo(clubId);
|
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>
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 플랜 구독 (기존 기능 - 레거시)
|
// 플랜 구독 (기존 기능 - 레거시)
|
||||||
|
|||||||
Reference in New Issue
Block a user