정리
This commit is contained in:
@@ -22,15 +22,17 @@ exports.publicEventEntry = async (req, res) => {
|
|||||||
return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 클럽의 구독 활성 여부 확인
|
// 클럽의 구독 활성 여부 확인 - 가장 최근 구독 정보 가져오기
|
||||||
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
||||||
where: { clubId: event.clubId, status: 'active' },
|
where: { clubId: event.clubId },
|
||||||
include: [{ model: sequelize.models.SubscriptionPlan }]
|
include: [{ model: sequelize.models.SubscriptionPlan }],
|
||||||
|
order: [['endDate', 'DESC']] // 가장 최근 구독 정보를 가져오기 위해 만료일 기준 내림차순 정렬
|
||||||
});
|
});
|
||||||
|
|
||||||
// 구독이 비활성화된 경우 접근 제한
|
// 구독이 없거나 만료된 경우 접근 제한
|
||||||
if (!clubSubscription) {
|
const now = new Date();
|
||||||
return res.status(403).json({ message: '클럽의 구독이 비활성화되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
|
||||||
|
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = null;
|
let token = null;
|
||||||
@@ -263,14 +265,21 @@ exports.addPublicGuest = async (req, res) => {
|
|||||||
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
|
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
|
||||||
const memberCount = await Member.count({ where: { clubId: event.clubId } });
|
const memberCount = await Member.count({ where: { clubId: event.clubId } });
|
||||||
|
|
||||||
// 클럽의 구독 정보 가져오기
|
// 클럽의 구독 정보 가져오기 - 가장 최근 구독 정보
|
||||||
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
|
||||||
where: { clubId: event.clubId, status: 'active' },
|
where: { clubId: event.clubId },
|
||||||
include: [{ model: sequelize.models.SubscriptionPlan }]
|
include: [{ model: sequelize.models.SubscriptionPlan }],
|
||||||
|
order: [['endDate', 'DESC']] // 가장 최근 구독 정보를 가져오기 위해 만료일 기준 내림차순 정렬
|
||||||
});
|
});
|
||||||
|
|
||||||
// 구독 플랜이 있고 회원 수가 최대치를 초과하는 경우
|
// 구독이 없거나 만료된 경우 접근 제한
|
||||||
if (clubSubscription && clubSubscription.SubscriptionPlan) {
|
const now = new Date();
|
||||||
|
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
|
||||||
|
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 회원 수가 최대치를 초과하는 경우
|
||||||
|
if (clubSubscription.SubscriptionPlan) {
|
||||||
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
|
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
|
||||||
if (memberCount >= maxMembers) {
|
if (memberCount >= maxMembers) {
|
||||||
return res.status(403).json({
|
return res.status(403).json({
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ module.exports = {
|
|||||||
up: async (queryInterface, Sequelize) => {
|
up: async (queryInterface, Sequelize) => {
|
||||||
// 각 클럽 5개에 대해 2개의 구독(플랜 1, 2)을 연결
|
// 각 클럽 5개에 대해 2개의 구독(플랜 1, 2)을 연결
|
||||||
const clubSubscriptions = [];
|
const clubSubscriptions = [];
|
||||||
|
const now = new Date();
|
||||||
for (let clubId = 1; clubId <= 5; clubId++) {
|
for (let clubId = 1; clubId <= 5; clubId++) {
|
||||||
const planId = Math.floor(Math.random() * 2) + 1; // 랜덤으로 플랜 1, 2 중 하나 선택
|
const planId = Math.floor(Math.random() * 2) + 1; // 랜덤으로 플랜 1, 2 중 하나 선택
|
||||||
const startDate = new Date(2025, 4, 1 + clubId);
|
const startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + clubId);
|
||||||
const endDate = new Date(startDate.getFullYear(), startDate.getMonth()+1, startDate.getDate());
|
const endDate = new Date(startDate.getFullYear(), startDate.getMonth()+clubId, startDate.getDate());
|
||||||
clubSubscriptions.push({
|
clubSubscriptions.push({
|
||||||
clubId,
|
clubId,
|
||||||
planId,
|
planId,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module.exports = {
|
|||||||
up: async (queryInterface, Sequelize) => {
|
up: async (queryInterface, Sequelize) => {
|
||||||
// 각 클럽마다 3개의 이벤트 생성
|
// 각 클럽마다 3개의 이벤트 생성
|
||||||
const events = [];
|
const events = [];
|
||||||
|
const now = new Date();
|
||||||
for (let clubId = 1; clubId <= 5; clubId++) {
|
for (let clubId = 1; clubId <= 5; clubId++) {
|
||||||
for (let i = 1; i <= 3; i++) {
|
for (let i = 1; i <= 3; i++) {
|
||||||
events.push({
|
events.push({
|
||||||
@@ -9,15 +10,15 @@ module.exports = {
|
|||||||
title: `클럽${clubId} 이벤트${i}`,
|
title: `클럽${clubId} 이벤트${i}`,
|
||||||
description: `클럽${clubId}의 이벤트${i} 설명입니다.`,
|
description: `클럽${clubId}의 이벤트${i} 설명입니다.`,
|
||||||
eventType: '정기전', // ENUM 값 중 하나
|
eventType: '정기전', // ENUM 값 중 하나
|
||||||
startDate: new Date(2025, 3, 10 + i, 19, 0, 0),
|
startDate: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 20, 0, 0),
|
||||||
endDate: new Date(2025, 3, 10 + i, 22, 0, 0),
|
endDate: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 23, 0, 0),
|
||||||
location: `클럽${clubId} 이벤트장소${i}`,
|
location: `클럽${clubId} 이벤트장소${i}`,
|
||||||
gameCount: 3,
|
gameCount: 3,
|
||||||
participantFee: 10000,
|
participantFee: 10000,
|
||||||
maxParticipants: 30,
|
maxParticipants: 30,
|
||||||
registrationDeadline: new Date(2025, 3, 9 + i, 23, 59, 59),
|
registrationDeadline: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 18, 0, 0),
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
status: '활성', // ENUM 값 중 하나
|
status: '준비', // ENUM 값 중 하나
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date()
|
updatedAt: new Date()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -112,8 +112,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="plan-details">
|
<div class="plan-details">
|
||||||
<div class="plan-detail-item">
|
<div>
|
||||||
<span class="detail-label">최대 회원수:</span>
|
<span class="detail-label mr-3">최대 회원수:</span>
|
||||||
<span class="detail-value">{{ selectedPlan.maxMembers || '제한 없음' }}</span>
|
<span class="detail-value">{{ selectedPlan.maxMembers || '제한 없음' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1403,7 +1403,6 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
flex: 1 1 calc(33% - 15px);
|
flex: 1 1 calc(33% - 15px);
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
@@ -1411,11 +1410,6 @@ onMounted(async () => {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-item:hover {
|
|
||||||
background-color: #f1f3f5;
|
|
||||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-item i {
|
.feature-item i {
|
||||||
color: #4CAF50;
|
color: #4CAF50;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
.text-red {
|
||||||
|
color: var(--p-red-500) !important;
|
||||||
|
}
|
||||||
.event-public-wrapper {
|
.event-public-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
|
|||||||
@@ -2,26 +2,29 @@
|
|||||||
<div class="event-public-wrapper">
|
<div class="event-public-wrapper">
|
||||||
<h1 v-if="clubName" class="p-text-primary p-text-bold p-text-lg p-mb-2">{{ clubName }}</h1>
|
<h1 v-if="clubName" class="p-text-primary p-text-bold p-text-lg p-mb-2">{{ clubName }}</h1>
|
||||||
<div class="event-public-card">
|
<div class="event-public-card">
|
||||||
<div v-if="loading" class="p-text-center p-mb-4">로딩 중...</div>
|
<div v-if="loading" class="text-center">로딩 중...</div>
|
||||||
<div v-else-if="error" class="p-text-center p-mb-4">{{ error }}</div>
|
<div v-else-if="error" class="text-center">
|
||||||
|
<i class="pi pi-exclamation-triangle text-red block"></i>
|
||||||
|
<span class="text-red">{{ error }}</span>
|
||||||
|
</div>
|
||||||
<div v-else-if="!isAuthenticated">
|
<div v-else-if="!isAuthenticated">
|
||||||
<Card>
|
<Card>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="event-password-form flex flex-column p-ai-center p-mt-6 p-mb-6">
|
<div class="event-password-form flex flex-column p-ai-center mt-6 mb-6">
|
||||||
<i class="pi pi-lock p-mb-3 event-password-lock"></i>
|
<i class="pi pi-lock mb-3 event-password-lock"></i>
|
||||||
<div class="p-text-center p-mb-3 p-text-lg event-password-desc">이 이벤트는 비밀번호가 필요합니다.<br>비밀번호를 입력해 주세요.</div>
|
<div class="p-text-center mb-3 p-text-lg event-password-desc">이 이벤트는 비밀번호가 필요합니다.<br>비밀번호를 입력해 주세요.</div>
|
||||||
<InputText v-model="inputPassword" type="password" placeholder="비밀번호 입력" class="p-mb-2 event-password-input"
|
<InputText v-model="inputPassword" type="password" placeholder="비밀번호 입력" class="p-mb-2 event-password-input"
|
||||||
@keyup.enter="verifyPassword" autofocus />
|
@keyup.enter="verifyPassword" autofocus />
|
||||||
<Button label="확인" @click="verifyPassword" class="p-mb-2 event-password-btn" :loading="loading" />
|
<Button label="확인" @click="verifyPassword" class="p-mb-2 event-password-btn" :loading="loading" />
|
||||||
<div v-if="passwordError" class="p-error p-mt-1 event-password-error">{{ passwordError }}</div>
|
<div v-if="passwordError" class="p-error mt-1 event-password-error">{{ passwordError }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<template v-if="event">
|
<template v-if="event">
|
||||||
<Divider class="p-mb-4" />
|
<Divider class="mb-4" />
|
||||||
<div class="event-tag">
|
<div class="event-tag mb-2">
|
||||||
<Tag :value="event.eventType" class="event-type-tag" />
|
<Tag :value="event.eventType" class="event-type-tag" />
|
||||||
<Tag :value="event.status"
|
<Tag :value="event.status"
|
||||||
:severity="event.status === '완료' ? 'success' : event.status === '활성' ? 'info' : 'warning'"
|
:severity="event.status === '완료' ? 'success' : event.status === '활성' ? 'info' : 'warning'"
|
||||||
@@ -31,34 +34,34 @@
|
|||||||
<div class="flex justify-content-end gap-2">
|
<div class="flex justify-content-end gap-2">
|
||||||
<Button :label="joinButtonLabel" class="p-button-lg p-button-primary" @click="showJoinDialog = true" v-if="canJoin" />
|
<Button :label="joinButtonLabel" class="p-button-lg p-button-primary" @click="showJoinDialog = true" v-if="canJoin" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-content-start p-ai-center p-mb-4">
|
<div class="flex justify-content-start p-ai-center mb-4">
|
||||||
<h2 class="event-title-inline p-text-lg p-text-bold">
|
<h2 class="event-title-inline p-text-lg p-text-bold">
|
||||||
{{ event.title }}
|
{{ event.title }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-text-secondary p-mb-3">
|
<div class="p-text-secondary mb-3">
|
||||||
<div class="info-row">
|
<div class="info-row">
|
||||||
<i class="pi pi-calendar p-mr-5" /><strong>기간</strong>
|
<i class="pi pi-calendar mr-1" /><strong>기간:</strong>
|
||||||
{{ formatDate(event.startDate) }}
|
{{ formatDate(event.startDate) }}
|
||||||
<template v-if="event.endDate">~ {{ formatDate(event.endDate) }}</template>
|
<template v-if="event.endDate">~ {{ formatDate(event.endDate) }}</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.registrationDeadline" class="info-row">
|
<div v-if="event.registrationDeadline" class="info-row" :class="isRegistrationClosed ? 'text-red' : ''">
|
||||||
<i class="pi pi-calendar-times p-mr-2" /><strong>마감</strong> {{ formatDate(event.registrationDeadline)
|
<i class="pi pi-calendar-times mr-1" /><strong>마감:</strong> {{ formatDate(event.registrationDeadline)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.location" class="info-row">
|
<div v-if="event.location" class="info-row">
|
||||||
<i class="pi pi-map-marker p-mr-2" /><strong>장소</strong> {{ event.location }}
|
<i class="pi pi-map-marker mr-1" /><strong>장소:</strong> {{ event.location }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.participantFee" class="info-row">
|
<div v-if="event.participantFee" class="info-row">
|
||||||
<i class="pi pi-credit-card p-mr-2" /><strong>참가비</strong> {{
|
<i class="pi pi-credit-card mr-1" /><strong>참가비:</strong> {{
|
||||||
Number(event.participantFee).toLocaleString('ko-KR') }}원
|
Number(event.participantFee).toLocaleString('ko-KR') }}원
|
||||||
</div>
|
</div>
|
||||||
<div v-if="event.maxParticipants" class="info-row">
|
<div v-if="event.maxParticipants" class="info-row">
|
||||||
<i class="pi pi-user p-mr-2" /><strong>참여</strong> {{ participants.length }}/{{ event.maxParticipants
|
<i class="pi pi-user mr-1" /><strong>참여:</strong> {{ participants.length }}/{{ event.maxParticipants
|
||||||
}}명
|
}}명
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-text-secondary p-mb-4">{{ event.description || '' }}</div>
|
<div class="p-text-secondary mb-4">{{ event.description || '' }}</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
<!-- 팀별 게임별 점수/등수 표 -->
|
<!-- 팀별 게임별 점수/등수 표 -->
|
||||||
</div>
|
</div>
|
||||||
@@ -208,12 +211,21 @@ const isReady = computed(() => event.value.status === '준비');
|
|||||||
const isActive = computed(() => event.value.status === '활성');
|
const isActive = computed(() => event.value.status === '활성');
|
||||||
const isFinished = computed(() => event.value.status === '완료');
|
const isFinished = computed(() => event.value.status === '완료');
|
||||||
|
|
||||||
const canJoin = computed(() => (isReady.value || isActive.value) && isAuthenticated.value);
|
// 신청 마감일시 체크
|
||||||
const canAddGuest = computed(() => isReady.value && isAuthenticated.value);
|
const isRegistrationClosed = computed(() => {
|
||||||
|
if (!event.value.registrationDeadline) return false;
|
||||||
|
const now = new Date();
|
||||||
|
const deadline = new Date(event.value.registrationDeadline);
|
||||||
|
return now > deadline;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 신청 마감일시가 지나지 않았고, 이벤트가 준비 또는 활성 상태이며, 인증되었을 때만 참가 신청 가능
|
||||||
|
const canJoin = computed(() => (isReady.value || isActive.value) && isAuthenticated.value && !isRegistrationClosed.value);
|
||||||
const canEditParticipant = computed(() => (isReady.value || isActive.value) && isAuthenticated.value);
|
const canEditParticipant = computed(() => (isReady.value || isActive.value) && isAuthenticated.value);
|
||||||
const canInputScore = computed(() => isActive.value && isAuthenticated.value);
|
const canInputScore = computed(() => isActive.value && isAuthenticated.value);
|
||||||
|
|
||||||
const joinButtonLabel = computed(() => {
|
const joinButtonLabel = computed(() => {
|
||||||
|
if (isRegistrationClosed.value) return '신청 마감';
|
||||||
if (isReady.value) return '참가 신청/수정';
|
if (isReady.value) return '참가 신청/수정';
|
||||||
if (isActive.value) return '참가자 정보 수정';
|
if (isActive.value) return '참가자 정보 수정';
|
||||||
return '참가자 정보';
|
return '참가자 정보';
|
||||||
|
|||||||
Reference in New Issue
Block a user