250613
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<nav
|
||||
<!-- <nav
|
||||
class="-mb-6 flex flex-wrap justify-center gap-x-6 sm:gap-x-12 gap-y-3 text-sm"
|
||||
aria-label="Footer Navigation"
|
||||
>
|
||||
@@ -31,7 +31,7 @@
|
||||
>
|
||||
{{ item.name }}
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</nav> -->
|
||||
|
||||
<hr class="my-8 border-gray-300 dark:border-gray-700 w-1/2 mx-auto" />
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
>
|
||||
<!-- MobileSidebar Open Button -->
|
||||
<div class="flex justify-start">
|
||||
<button
|
||||
<!-- <button
|
||||
type="button"
|
||||
class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700 dark:text-white"
|
||||
@click="mobileMenuOpen = true"
|
||||
aria-label="Open main menu"
|
||||
>
|
||||
<Bars3Icon class="size-6" aria-hidden="true" />
|
||||
</button>
|
||||
</button> -->
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<!-- Desktop HeaderActions -->
|
||||
<div class="flex justify-end">
|
||||
<HeaderActions />
|
||||
<!-- <HeaderActions /> -->
|
||||
</div>
|
||||
</nav>
|
||||
<!-- MobileSidebar -->
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
<template>
|
||||
>
|
||||
<form @submit.prevent="onSubmit" class="mx-auto mt-10 max-w-lg space-y-8">
|
||||
<!-- 1) 성함 -->
|
||||
<div>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
성함 (Your Name)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
id="name"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 2) 와디즈 결제 번호 -->
|
||||
<div>
|
||||
<label
|
||||
for="paymentId"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
와디즈 결제 번호 (Wadiz Payment ID)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.paymentId"
|
||||
type="text"
|
||||
id="paymentId"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 3) 메일 -->
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
메일 (Email)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
id="email"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 4) 본인 연락처 -->
|
||||
<div>
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
본인 연락처 (Your Phone)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.phone"
|
||||
type="tel"
|
||||
id="phone"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
placeholder="010-1234-5678"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 5) 본인 참석 여부 -->
|
||||
<div>
|
||||
<span class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>본인 참석 여부 (Are you attending?)</span
|
||||
>
|
||||
<div class="mt-2 flex items-center gap-6">
|
||||
<label class="inline-flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="attending"
|
||||
value="yes"
|
||||
v-model="form.attending"
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300">예 (Yes)</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
name="attending"
|
||||
value="no"
|
||||
v-model="form.attending"
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300">아니오 (No)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6) If “아니오”, show alternate attendee fields -->
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="form.attending === 'no'" class="space-y-4">
|
||||
<!-- 대리 참석자 성함 -->
|
||||
<div>
|
||||
<label
|
||||
for="altName"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
대리 참석자 성함 (Alternate Attendee Name)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.altName"
|
||||
type="text"
|
||||
id="altName"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
</div>
|
||||
<!-- 대리 참석자 연락처 -->
|
||||
<div>
|
||||
<label
|
||||
for="altPhone"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
대리 참석자 연락처 (Alternate Attendee Phone)
|
||||
</label>
|
||||
<input
|
||||
v-model="form.altPhone"
|
||||
type="tel"
|
||||
id="altPhone"
|
||||
required
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
placeholder="010-1234-5678"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 7) 기타 문의 사항 -->
|
||||
<div>
|
||||
<label
|
||||
for="remarks"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
기타 문의 사항 (Other Remarks)
|
||||
</label>
|
||||
<textarea
|
||||
v-model="form.remarks"
|
||||
id="remarks"
|
||||
rows="4"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
placeholder="예: 채팅으로 연락해주세요."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="submitting"
|
||||
class="inline-flex w-full justify-center rounded-md bg-indigo-600 dark:bg-indigo-500 px-4 py-2 text-base font-semibold text-white shadow-sm hover:bg-indigo-500 dark:hover:bg-indigo-400 focus:outline-none focus:ring-2 focus:ring-indigo-600 disabled:opacity-50"
|
||||
>
|
||||
{{ submitting ? '전송 중…' : '제출하기' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Success / Error Message -->
|
||||
<p
|
||||
v-if="message"
|
||||
:class="
|
||||
messageError
|
||||
? 'mt-4 text-sm text-red-600'
|
||||
: 'mt-4 text-sm text-green-600'
|
||||
"
|
||||
>
|
||||
{{ message }}
|
||||
</p>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useNuxtApp } from '#app';
|
||||
|
||||
/**
|
||||
* This form will write a document to Firestore collection "wadizSubmissions".
|
||||
* Make sure you've set up a Nuxt plugin that provides `$firebase.firestore()` if you haven't already.
|
||||
*/
|
||||
|
||||
const submitting = ref(false);
|
||||
const message = ref('');
|
||||
const messageError = ref(false);
|
||||
|
||||
// Form state
|
||||
const form = ref({
|
||||
name: '',
|
||||
paymentId: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
attending: 'yes', // default “yes”
|
||||
altName: '',
|
||||
altPhone: '',
|
||||
remarks: '',
|
||||
});
|
||||
|
||||
async function onSubmit() {
|
||||
submitting.value = true;
|
||||
message.value = '';
|
||||
messageError.value = false;
|
||||
|
||||
// Basic validation
|
||||
if (
|
||||
!form.value.name ||
|
||||
!form.value.paymentId ||
|
||||
!form.value.email ||
|
||||
!form.value.phone
|
||||
) {
|
||||
message.value = '필수 항목을 모두 채워주세요.';
|
||||
messageError.value = true;
|
||||
submitting.value = false;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
form.value.attending === 'no' &&
|
||||
(!form.value.altName || !form.value.altPhone)
|
||||
) {
|
||||
message.value = '대리 참석자 정보를 모두 입력해주세요.';
|
||||
messageError.value = true;
|
||||
submitting.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare payload
|
||||
const payload = {
|
||||
name: form.value.name,
|
||||
paymentId: form.value.paymentId,
|
||||
email: form.value.email,
|
||||
phone: form.value.phone,
|
||||
attending: form.value.attending === 'yes',
|
||||
altName: form.value.attending === 'no' ? form.value.altName : null,
|
||||
altPhone: form.value.attending === 'no' ? form.value.altPhone : null,
|
||||
remarks: form.value.remarks,
|
||||
submittedAt: new Date(),
|
||||
};
|
||||
|
||||
try {
|
||||
// Save to Firestore (collection “wadizSubmissions”)
|
||||
message.value = '성공적으로 제출되었습니다!';
|
||||
messageError.value = false;
|
||||
|
||||
// Clear form
|
||||
form.value.name = '';
|
||||
form.value.paymentId = '';
|
||||
form.value.email = '';
|
||||
form.value.phone = '';
|
||||
form.value.attending = 'yes';
|
||||
form.value.altName = '';
|
||||
form.value.altPhone = '';
|
||||
form.value.remarks = '';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
message.value = '제출 중 오류가 발생했습니다. 다시 시도해주세요.';
|
||||
messageError.value = true;
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Fade transition for the conditional fields */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
42
bobu/app/components/WadizWelcome.vue
Normal file
42
bobu/app/components/WadizWelcome.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="isolate bg-white dark:bg-gray-900 px-6 py-12 sm:py-12 lg:px-8">
|
||||
<!-- Section -->
|
||||
<section
|
||||
class="isolate bg-white dark:bg-gray-900 px-6 py-16 sm:py-24 lg:px-8"
|
||||
>
|
||||
<div class="mx-auto max-w-2xl lg:max-w-4xl">
|
||||
<p
|
||||
class="text-center text-2xl font-extrabold text-indigo-500 dark:text-indigo-400"
|
||||
>
|
||||
와디즈 펀딩 참여자 발송 페이지
|
||||
</p>
|
||||
<figure class="mt-10">
|
||||
<blockquote
|
||||
class="text-center text-lg font-normal text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
<p>안녕하세요, 메이커 보부입니다!</p>
|
||||
<p>보부의 첫 여정에 마음을 더해주셔서 감사합니다.</p>
|
||||
<p>여러분의 따뜻한 응원 덕분에,</p>
|
||||
<p>정선의 자연속 1박 2일의 쉼을 준비할 수 있었습니다.</p>
|
||||
<p>자연에 기대어, 잠시 천천히 머물러보는 시간을 선물해드릴게요.</p>
|
||||
<p class="mt-4 font-semibold">
|
||||
“보부와 함께할 하루, 이제 예약으로 이어집니다."
|
||||
</p>
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
<NuxtLink to="/wadiz/upload">
|
||||
<button
|
||||
class="block w-full bg-indigo-600 text-white py-3 px-3 rounded transition hover:bg-indigo-700 mt-12 font-semibold dark:bg-indigo-500 dark:hover:bg-indigo-600 disabled:opacity-50 dark:disabled:opacity-60"
|
||||
>
|
||||
참여자 정보 입력하기
|
||||
</button>
|
||||
</NuxtLink>
|
||||
</section>
|
||||
|
||||
<!-- Form -->
|
||||
<ClientOnly> </ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
671
bobu/app/components/boards/wadiz/UploadWadizForm.vue
Normal file
671
bobu/app/components/boards/wadiz/UploadWadizForm.vue
Normal file
@@ -0,0 +1,671 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-5xl w-full py-4 px-6 items-center">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 pb-8">
|
||||
<VeeForm @submit="handleUpload" v-slot="{ isSubmitting }">
|
||||
<div class="mx-auto mt-10 max-w-lg space-y-8">
|
||||
<!-- 1) 와디즈 결제 번호 -->
|
||||
<div>
|
||||
<label
|
||||
for="paymentId"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
와디즈 결제 번호
|
||||
</label>
|
||||
<VeeField
|
||||
name="paymentId"
|
||||
id="paymentId"
|
||||
type="text"
|
||||
rules="required"
|
||||
v-model="boardsData.paymentId"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="paymentId"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 2) 성함 -->
|
||||
<div>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
성함
|
||||
</label>
|
||||
<VeeField
|
||||
name="name"
|
||||
id="name"
|
||||
type="text"
|
||||
rules="required"
|
||||
v-model="boardsData.name"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage name="name" class="text-red-500 text-sm mt-1" />
|
||||
</div>
|
||||
|
||||
<!-- 3) 주소 -->
|
||||
<div>
|
||||
<label
|
||||
for="address"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
주소
|
||||
</label>
|
||||
<VeeField
|
||||
name="address"
|
||||
id="address"
|
||||
type="text"
|
||||
rules="required"
|
||||
v-model="boardsData.address"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600"
|
||||
/>
|
||||
<VeeErrorMessage name="address" class="text-red-500 text-sm mt-1" />
|
||||
</div>
|
||||
|
||||
<!-- 4) 메일 -->
|
||||
<div>
|
||||
<label
|
||||
for="email"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
메일
|
||||
</label>
|
||||
<VeeField
|
||||
name="email"
|
||||
id="email"
|
||||
type="email"
|
||||
rules="required|email"
|
||||
v-model="boardsData.email"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage name="email" class="text-red-500 text-sm mt-1" />
|
||||
</div>
|
||||
|
||||
<!-- 5) 본인 연락처 -->
|
||||
<div>
|
||||
<label
|
||||
for="phone"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
본인 연락처 (Your Phone)
|
||||
</label>
|
||||
<VeeField
|
||||
name="phone"
|
||||
id="phone"
|
||||
type="tel"
|
||||
rules="required"
|
||||
v-model="boardsData.phone"
|
||||
placeholder="010-1234-5678"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage name="phone" class="text-red-500 text-sm mt-1" />
|
||||
</div>
|
||||
<!-- 6) 긴급 연락처 -->
|
||||
<div>
|
||||
<label
|
||||
for="emergencyPhone"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
긴급 연락처 | 본인 외 제3자
|
||||
</label>
|
||||
<VeeField
|
||||
name="emergencyPhone"
|
||||
id="emergencyPhone"
|
||||
type="tel"
|
||||
rules="required"
|
||||
v-model="boardsData.emergencyPhone"
|
||||
placeholder="010-0000-0000"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="emergencyPhone"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 7) 예약 일정 선택 -->
|
||||
<div>
|
||||
<label
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
예약 일정 선택
|
||||
</label>
|
||||
<div class="mt-2 flex gap-4">
|
||||
<!-- 시작일 -->
|
||||
<div class="w-1/2">
|
||||
<label
|
||||
for="scheduleStart"
|
||||
class="mb-1 block text-sm text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
시작일
|
||||
</label>
|
||||
<Datepicker
|
||||
v-model="scheduleStartDate"
|
||||
:highlight="highlightedDates"
|
||||
:allowed-dates="allowedDates"
|
||||
:enable-time-picker="false"
|
||||
auto-apply
|
||||
id="scheduleStart"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 종료일 -->
|
||||
<div class="w-1/2">
|
||||
<label
|
||||
for="scheduleEnd"
|
||||
class="mb-1 block text-sm text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
종료일
|
||||
</label>
|
||||
<input
|
||||
v-model="boardsData.scheduleEnd"
|
||||
id="scheduleEnd"
|
||||
type="text"
|
||||
disabled
|
||||
placeholder="자동 계산 (1박 2일)"
|
||||
class="w-full rounded-md bg-gray-100 dark:bg-gray-700 px-3 py-2 text-gray-400 dark:text-gray-400 border dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
예약불가 날짜: 2025년 10월 3일(금)~5일(일), 연박 불가 / 금·토·일
|
||||
중 1박만 가능
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 8) 본인 참석 여부 -->
|
||||
<div>
|
||||
<span
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
본인 참석 여부 (Are you attending?)
|
||||
</span>
|
||||
<div class="mt-2 flex items-center gap-6">
|
||||
<label class="inline-flex items-center">
|
||||
<VeeField
|
||||
name="attending"
|
||||
type="radio"
|
||||
:value="'yes'"
|
||||
v-model="boardsData.attending"
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300"
|
||||
>예 (Yes)</span
|
||||
>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<VeeField
|
||||
name="attending"
|
||||
type="radio"
|
||||
:value="'no'"
|
||||
v-model="boardsData.attending"
|
||||
class="h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"
|
||||
/>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300"
|
||||
>아니오 (No)</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
<VeeErrorMessage
|
||||
name="attending"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 8-2) If "아니오", show alternate attendee fields -->
|
||||
<div v-if="boardsData.attending === 'no'" class="space-y-4">
|
||||
<!-- 대리 참석자 성함 -->
|
||||
<div>
|
||||
<label
|
||||
for="altName"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
대리 참석자 성함 (Alternate Attendee Name)
|
||||
</label>
|
||||
<VeeField
|
||||
name="altName"
|
||||
id="altName"
|
||||
type="text"
|
||||
v-model="boardsData.altName"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="altName"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 대리 참석자 연락처 -->
|
||||
<div>
|
||||
<label
|
||||
for="altPhone"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
대리 참석자 연락처 (Alternate Attendee Phone)
|
||||
</label>
|
||||
<VeeField
|
||||
name="altPhone"
|
||||
id="altPhone"
|
||||
type="tel"
|
||||
v-model="boardsData.altPhone"
|
||||
placeholder="010-1234-5678"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border outline-1 outline-offset-1 outline-gray-300 dark:outline-gray-600 placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:outline-2 focus:outline-indigo-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="altPhone"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 9) 참여 동반자 정보 -->
|
||||
<div>
|
||||
<label
|
||||
for="companions"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
참여 동반자 정보 (선택)
|
||||
</label>
|
||||
<VeeField
|
||||
name="companions"
|
||||
id="companions"
|
||||
type="text"
|
||||
v-model="boardsData.companions"
|
||||
placeholder="동반자 성함 등"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border dark:border-gray-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="companions"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 10) 알레르기나 건강 특이사항 (선택) -->
|
||||
<div>
|
||||
<label
|
||||
for="healthNotes"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
알레르기나 건강 특이사항 (선택)
|
||||
</label>
|
||||
<VeeField
|
||||
name="healthNotes"
|
||||
id="healthNotes"
|
||||
v-model="boardsData.healthNotes"
|
||||
as="textarea"
|
||||
rows="2"
|
||||
placeholder="예: 견과류 알레르기"
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border dark:border-gray-600"
|
||||
/>
|
||||
<VeeErrorMessage
|
||||
name="healthNotes"
|
||||
class="text-red-500 text-sm mt-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 11) 전달하고 싶은 말 (선택) -->
|
||||
<div>
|
||||
<label
|
||||
for="remarks"
|
||||
class="block text-sm font-medium text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
전달하고 싶은 말 (선택)
|
||||
</label>
|
||||
<VeeField
|
||||
name="remarks"
|
||||
id="remarks"
|
||||
v-model="boardsData.remarks"
|
||||
as="textarea"
|
||||
rows="4"
|
||||
placeholder="예: 채팅으로 연락해주세요."
|
||||
class="mt-1 block w-full rounded-md bg-white dark:bg-gray-800 px-3.5 py-2 text-base text-gray-900 dark:text-gray-100 border dark:border-gray-600"
|
||||
/>
|
||||
<VeeErrorMessage name="remarks" class="text-red-500 text-sm mt-1" />
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<button
|
||||
type="submit"
|
||||
class="block w-full bg-indigo-600 text-white py-3 px-3 rounded transition hover:bg-indigo-700 mt-12 font-semibold dark:bg-indigo-500 dark:hover:bg-indigo-600 disabled:opacity-50 dark:disabled:opacity-60"
|
||||
:disabled="in_submission"
|
||||
>
|
||||
<div
|
||||
v-if="show_alert"
|
||||
:class="[
|
||||
alert_variant, // Ensure this variable contains dark: variants
|
||||
'text-white text-center font-bold p-4 rounded mb-4',
|
||||
]"
|
||||
>
|
||||
{{ alert_msg }}
|
||||
</div>
|
||||
<div v-show="!in_submission">
|
||||
{{ isEdit ? '수정하기' : '제출하기' }}
|
||||
</div>
|
||||
<div v-if="in_submission">
|
||||
<font-awesome-icon :icon="['fas', 'spinner']" spin />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</VeeForm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
reactive,
|
||||
computed,
|
||||
} from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { fetchLatestDocumentNumber } from '@/utils/firebaseUtils';
|
||||
import {
|
||||
resetFileSlots,
|
||||
getFilesFromUploads,
|
||||
createBoardsData,
|
||||
uploadFiles,
|
||||
handleUpdateBoard,
|
||||
handleCreateBoard,
|
||||
getDeleteFileIndexesFromUploads,
|
||||
createEmptyUploadFileData,
|
||||
getBoardDocRef,
|
||||
} from '@/utils/boardUtils';
|
||||
import {
|
||||
Form as VeeForm,
|
||||
Field as VeeField,
|
||||
ErrorMessage as VeeErrorMessage,
|
||||
} from 'vee-validate';
|
||||
import { syncBoardAndUploadsData } from '@/utils/boardUtils';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
//types
|
||||
import { UploadSettings } from '@/data/config';
|
||||
import type {
|
||||
WadizBoard,
|
||||
BoardItem,
|
||||
ThumbnailData,
|
||||
FileItem,
|
||||
UploadFileData,
|
||||
UploadsData,
|
||||
} from '@/types';
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
isEdit: { type: Boolean, default: false },
|
||||
board: { type: Object as PropType<BoardItem>, default: () => ({}) },
|
||||
});
|
||||
const emit = defineEmits(['update-success', 'success']);
|
||||
const userStore = useUserStore();
|
||||
const userId = computed(() => userStore.docId);
|
||||
|
||||
//customize
|
||||
const { $firebase } = useNuxtApp();
|
||||
const wadizesCollection = $firebase.wadizesCollection;
|
||||
const currentCollection = wadizesCollection;
|
||||
const currentBoard = 'wadiz';
|
||||
const compData = {
|
||||
title: '공지사항 | NOTICE',
|
||||
};
|
||||
//loading Message
|
||||
const loadingMessage = 'Uploading! 잠시만 기다려주세요...';
|
||||
const isUploading = ref(false);
|
||||
//alert
|
||||
const { show_alert, alert_variant, alert_msg, showAlert } = useAlert();
|
||||
|
||||
// Validation
|
||||
const validateInput = (): boolean => {
|
||||
if (!boardsData.value.paymentId.trim()) {
|
||||
showAlert('와디즈 결제 번호는 필수입니다!', 'bg-red-500', true);
|
||||
in_submission.value = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
//state
|
||||
const in_submission = ref(false);
|
||||
const isEdit = ref(props.isEdit ?? false);
|
||||
const newBoard = ref(props.board);
|
||||
const boardsData: Ref<WadizBoard> = ref({
|
||||
docId: '',
|
||||
userId: '',
|
||||
title: '',
|
||||
description: '',
|
||||
boardState: { state: 'processing' },
|
||||
announcement: false,
|
||||
created: '',
|
||||
files: [],
|
||||
//depreciated
|
||||
boards_number: 0,
|
||||
thumbnail: { name: '', url: '' } as FileItem,
|
||||
ishidden: false,
|
||||
//wadizes
|
||||
name: '',
|
||||
paymentId: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
address: '',
|
||||
emergencyPhone: '',
|
||||
scheduleStart: '',
|
||||
scheduleEnd: '',
|
||||
attending: 'yes',
|
||||
altName: '',
|
||||
altPhone: '',
|
||||
companions: '',
|
||||
healthNotes: '',
|
||||
remarks: '',
|
||||
});
|
||||
|
||||
const thumbnailInput = ref<HTMLInputElement | null>(null);
|
||||
const thumbnail: ThumbnailData = {
|
||||
input: ref(null),
|
||||
preview: ref(''),
|
||||
deleteTrigger: ref(false),
|
||||
oldPreviewState: ref(false),
|
||||
uploadState: ref(false),
|
||||
previewState: ref(false),
|
||||
displayedName: ref(''),
|
||||
};
|
||||
const files = reactive<UploadFileData[]>([]);
|
||||
|
||||
const uploadsData: UploadsData = {
|
||||
thumbnail,
|
||||
files,
|
||||
};
|
||||
//FOR EDIT, Write
|
||||
|
||||
// firebase Upload
|
||||
const handleUpload = async () => {
|
||||
in_submission.value = true;
|
||||
|
||||
if (!validateInput()) {
|
||||
in_submission.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Validate plain files (UploadFileData[])
|
||||
const rejectedFiles = uploadsData.files
|
||||
.map((fileData) => fileData?.input)
|
||||
.filter(
|
||||
(file) => file instanceof File && !UploadSettings.isValidFile(file)
|
||||
);
|
||||
|
||||
if (rejectedFiles.length > 0) {
|
||||
const reason =
|
||||
rejectedFiles[0] instanceof File
|
||||
? UploadSettings.getInvalidFileReasonKey(rejectedFiles[0])
|
||||
: null;
|
||||
const msg = UploadSettings.UploadErrorMessages[reason!];
|
||||
showAlert(msg ?? 'Invalid file', 'bg-red-500');
|
||||
in_submission.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newBoardsNumber = await fetchLatestDocumentNumber(
|
||||
currentCollection,
|
||||
'boards_number'
|
||||
);
|
||||
|
||||
let boardPayload: WadizBoard = createBoardsData(
|
||||
boardsData.value,
|
||||
newBoardsNumber
|
||||
) as WadizBoard;
|
||||
|
||||
// ✅ Collect valid files and thumbnail
|
||||
const { thumbnail, files } = getFilesFromUploads(uploadsData);
|
||||
|
||||
if (isEdit.value) {
|
||||
if (!newBoard.value) {
|
||||
showAlert('기존 게시글 데이터를 찾을 수 없습니다.', 'bg-red-500');
|
||||
in_submission.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const oldFiles = newBoard.value.files ?? [];
|
||||
const oldThumbUrl = newBoard.value.thumbnail?.url ?? '';
|
||||
const deleteThumbnail = uploadsData.thumbnail.deleteTrigger.value;
|
||||
const deleteFileIndexes = getDeleteFileIndexesFromUploads(uploadsData);
|
||||
|
||||
boardPayload = (await uploadFiles({
|
||||
newBoardData: boardPayload,
|
||||
newThumbnail: thumbnail instanceof File ? thumbnail : null,
|
||||
newFiles: files,
|
||||
oldThumbnailUrl: oldThumbUrl,
|
||||
oldFiles,
|
||||
deleteThumbnail,
|
||||
deleteFileIndexes,
|
||||
currentBoard,
|
||||
})) as WadizBoard;
|
||||
|
||||
await handleUpdateBoard(boardPayload, currentCollection);
|
||||
showAlert('성공적으로 수정되었습니다', 'bg-green-800');
|
||||
emit('success');
|
||||
} else {
|
||||
const { docId: freshDocId } = getBoardDocRef(currentCollection);
|
||||
|
||||
boardPayload = {
|
||||
...boardPayload,
|
||||
docId: freshDocId,
|
||||
userId: userId.value,
|
||||
boardState: { state: 'processing' },
|
||||
};
|
||||
boardPayload = (await uploadFiles({
|
||||
newBoardData: boardPayload,
|
||||
newThumbnail: thumbnail,
|
||||
newFiles: files,
|
||||
currentBoard,
|
||||
})) as WadizBoard;
|
||||
|
||||
if (boardPayload.scheduleStart instanceof Date) {
|
||||
boardPayload.scheduleStart = format(
|
||||
boardPayload.scheduleStart,
|
||||
'yyyy-MM-dd'
|
||||
);
|
||||
}
|
||||
if (boardPayload.scheduleEnd instanceof Date) {
|
||||
boardPayload.scheduleEnd = format(
|
||||
boardPayload.scheduleEnd,
|
||||
'yyyy-MM-dd'
|
||||
);
|
||||
}
|
||||
await handleCreateBoard(boardPayload, currentCollection);
|
||||
showAlert('성공적으로 생성되었습니다', 'bg-green-800', true);
|
||||
emit('success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('업로드 중 에러 발생:', error);
|
||||
showAlert('업로드 실패: 파일 또는 데이터 에러', 'bg-red-500');
|
||||
} finally {
|
||||
in_submission.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// if (uploadsData.files.length === 0) {
|
||||
// addFileSlot(uploadsData.files);
|
||||
// }
|
||||
console.log('mounted', uploadsData);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.board,
|
||||
(newBoard) => {
|
||||
if (newBoard) {
|
||||
syncBoardAndUploadsData(
|
||||
newBoard,
|
||||
boardsData,
|
||||
uploadsData,
|
||||
createEmptyUploadFileData,
|
||||
resetFileSlots
|
||||
);
|
||||
console.log('newBoard', newBoard);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
//calender
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
import { format, addDays } from 'date-fns';
|
||||
|
||||
// Blocked weekend range (연박 불가)
|
||||
const blockedDates = ['2025-10-03', '2025-10-04', '2025-10-05'];
|
||||
|
||||
const allowedDates = computed(() => {
|
||||
const result: Date[] = [];
|
||||
const start = new Date('2025-06-13');
|
||||
const end = new Date('2025-12-01');
|
||||
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const date = new Date(d);
|
||||
const day = date.getDay(); // 0: Sun, 5: Fri, 6: Sat
|
||||
const dateStr = format(date, 'yyyy-MM-dd');
|
||||
const isUnavailable = dateStr >= '2025-10-03' && dateStr <= '2025-10-05';
|
||||
|
||||
const isWeekend = [5, 6, 0].includes(day);
|
||||
|
||||
if (isWeekend && !isUnavailable) {
|
||||
result.push(date);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
// Optional: Highlight allowed dates with a class
|
||||
const highlightedDates = computed(() => {
|
||||
const map: Partial<Record<number, string>> = {};
|
||||
allowedDates.value.forEach((date) => {
|
||||
map[date.getTime()] = 'dp-highlight-green';
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// Auto-calculate end date
|
||||
watch(
|
||||
() => boardsData.value.scheduleStart,
|
||||
(val) => {
|
||||
if (!val) return;
|
||||
const endDate = addDays(new Date(val as string), 1);
|
||||
boardsData.value.scheduleEnd = format(endDate, 'yyyy-MM-dd'); // ✅ Use ISO format
|
||||
}
|
||||
);
|
||||
|
||||
// Two-way binding date model
|
||||
const scheduleStartDate = computed({
|
||||
get: () =>
|
||||
boardsData.value.scheduleStart instanceof Date
|
||||
? boardsData.value.scheduleStart
|
||||
: new Date(),
|
||||
set: (val: Date) => {
|
||||
boardsData.value.scheduleStart = val;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.ck-editor__editable {
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
||||
0
bobu/app/components/boards/wadiz/WadizCalender.vue
Normal file
0
bobu/app/components/boards/wadiz/WadizCalender.vue
Normal file
22
bobu/app/components/boards/wadiz/WadizSuccess.vue
Normal file
22
bobu/app/components/boards/wadiz/WadizSuccess.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<section class="bg-white dark:bg-gray-900 py-16 px-6">
|
||||
<div class="max-w-2xl mx-auto text-center">
|
||||
<h1
|
||||
class="text-2xl sm:text-3xl font-bold text-gray-800 dark:text-white mb-6"
|
||||
>
|
||||
예약이 완료되었습니다. 감사합니다.
|
||||
</h1>
|
||||
<p class="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
정선의 자연, 그리고 보부의 하루에 함께해주셔서 진심으로 감사합니다.
|
||||
여러분의 한 걸음이 저희에겐 큰 의미였습니다.<br /><br />
|
||||
자연 속에서의 쉼, 차분히 준비해두겠습니다.<br />
|
||||
<span class="font-semibold text-indigo-600 dark:text-indigo-400"
|
||||
>곧, 정선에서 뵙겠습니다.</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
console.log('Success page mounted!');
|
||||
</script>
|
||||
@@ -6,10 +6,10 @@ export const COLORSELECTOR_BG = {
|
||||
};
|
||||
|
||||
export const LOGOS = {
|
||||
White: '/assets/img/logo/BOBU_LOGO_WHITE.webp',
|
||||
RedGaro: '/assets/img/logo/Garo_Red.webp',
|
||||
Red: '/assets/img/logo/BOBU_LOGO_RED.webp',
|
||||
WhiteGaro: '/assets/img/logo/Garo_White.webp',
|
||||
White: '/assets/img/logo/Bobu_White.webp',
|
||||
RedGaro: '/assets/img/logo/Bobu_Red.webp',
|
||||
Red: '/assets/img/logo/Bobu_Red.webp',
|
||||
WhiteGaro: '/assets/img/logo/Bobu_White.webp',
|
||||
};
|
||||
export const ABOUT_IMAGES = {
|
||||
manos: '/assets/img/shoot.jpg',
|
||||
|
||||
@@ -29,12 +29,12 @@ export const SOCIAL_LINKS = {
|
||||
|
||||
export const companyInfo = {
|
||||
name: '노마드보부', // Or the official name from registration
|
||||
registrationNumber: '693-82-00244',
|
||||
president: '배현일',
|
||||
address: '강원 정선군 정선읍 정선로 1324 2층',
|
||||
registrationNumber: '830-86-02932',
|
||||
president: '서지민',
|
||||
address: '강원 정선군 정선읍 정선로 1324, 2층',
|
||||
phone: '0507-1353-1868',
|
||||
fax: '0507-1353-1868',
|
||||
email: 'manoscoop@naver.com',
|
||||
email: 'bobu1104@naver.com',
|
||||
copyYear: new Date().getFullYear(), // Automatically get current year
|
||||
};
|
||||
// Upload Settings
|
||||
|
||||
@@ -61,6 +61,26 @@ export interface NavigationItem {
|
||||
}
|
||||
|
||||
// Board : Elements
|
||||
export type ReservationSubmission = {
|
||||
docId: string;
|
||||
userId: string;
|
||||
paymentId: string;
|
||||
name: string;
|
||||
address: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
emergencyPhone: string;
|
||||
scheduleStart: string;
|
||||
scheduleEnd: string | null;
|
||||
attending: boolean;
|
||||
altName?: string | null;
|
||||
altPhone?: string | null;
|
||||
companions?: string;
|
||||
healthNotes?: string;
|
||||
remarks?: string;
|
||||
submittedAt: Date;
|
||||
boardState?: { state: 'processing' | 'completed' };
|
||||
};
|
||||
|
||||
export type FileItem = {
|
||||
name: string;
|
||||
|
||||
7
bobu/app/pages/WadizSuccess.vue
Normal file
7
bobu/app/pages/WadizSuccess.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Wadiz Success</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
@@ -1,12 +1,17 @@
|
||||
<template>
|
||||
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
|
||||
<!-- Featured Projects Section -->
|
||||
<FeaturesCarousel />
|
||||
<!-- <FeaturesCarousel />
|
||||
<AboutSection1 />
|
||||
<AboutSection3 />
|
||||
<AboutSection3 /> -->
|
||||
<!-- <AppWadiz /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import AppWadiz from '~/pages/wadiz/index.vue';
|
||||
import { MAIN } from '~/data/assets';
|
||||
definePageMeta({
|
||||
redirect: '/wadiz',
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<template>
|
||||
<div class="isolate bg-white dark:bg-gray-900 px-6 py-12 sm:py-12 lg:px-8">
|
||||
<!-- Section -->
|
||||
<section
|
||||
class="isolate bg-white dark:bg-gray-900 px-6 py-16 sm:py-24 lg:px-8"
|
||||
>
|
||||
<div class="mx-auto max-w-2xl lg:max-w-4xl">
|
||||
<p
|
||||
class="text-center text-2xl font-extrabold text-indigo-500 dark:text-indigo-400"
|
||||
>
|
||||
와디즈 펀딩 참여자 발송 페이지
|
||||
</p>
|
||||
<figure class="mt-10">
|
||||
<blockquote
|
||||
class="text-center text-lg font-normal text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
<p>안녕하세요, 주식회사 보부입니다.</p>
|
||||
<p>
|
||||
지난 와디즈에서 진행한 정선 백패킹 포레스트 관련한 예약 페이지
|
||||
입니다.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
참여하신 분들은
|
||||
<strong class="text-gray-900 dark:text-gray-100"
|
||||
>6월 13일까지</strong
|
||||
>
|
||||
작성 부탁드립니다.
|
||||
</p>
|
||||
</blockquote>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Form -->
|
||||
<ClientOnly>
|
||||
<app-wadiz-form />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppWadizForm from '@/components/WadizForm.vue';
|
||||
</script>
|
||||
86
bobu/app/pages/wadiz/index.vue
Normal file
86
bobu/app/pages/wadiz/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div>
|
||||
<app-wadiz-welcome />
|
||||
<!-- <app-board-list
|
||||
v-model="selectedSort"
|
||||
:title="compdata.title"
|
||||
:sortOptions="sortOptions"
|
||||
:userRole="userRole"
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
:page-numbers="pageNumbers"
|
||||
:show-select-boxes="showSelectBoxes"
|
||||
:uploadRoute="currentUploadRoute"
|
||||
@toggle-select-boxes="showSelectBoxes = !showSelectBoxes"
|
||||
@delete-selected="onDeleteSelected"
|
||||
@go-to-page="onGoToPage"
|
||||
@prev-page="onPrevPage"
|
||||
@next-page="onNextPage"
|
||||
:isLoading="isLoading"
|
||||
:loadingMessage="loadingMessage"
|
||||
>
|
||||
<template #list>
|
||||
<app-board-list-single
|
||||
v-for="item in currentItems"
|
||||
:key="item.docId"
|
||||
:userId="item.userId"
|
||||
:item="item"
|
||||
:showSelectBox="showSelectBoxes"
|
||||
@select="onToggleSelect(item)"
|
||||
:iconName="['fas', 'bell']"
|
||||
:routeName="currentBoardRouteName"
|
||||
/>
|
||||
</template>
|
||||
</app-board-list> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppWadizWelcome from '~/components/WadizWelcome.vue';
|
||||
import AppBoardsHeader from '@/components/boards/BoardHeader.vue';
|
||||
import AppBoardList from '@/components/boards/BoardList.vue';
|
||||
import AppBoardListSingle from '@/components/boards/BoardListSingle.vue';
|
||||
import type { OrderByDirection, BoardAccessMode } from '@/types';
|
||||
|
||||
//customize
|
||||
const access: BoardAccessMode = 'public';
|
||||
const currentCollection = 'wadizes';
|
||||
const currentUploadRoute = '/wadiz/upload';
|
||||
const currentBoardRouteName = '/wadiz';
|
||||
const compData = {
|
||||
title: '공지사항',
|
||||
itemsPerPage: 20,
|
||||
defaultSort: 'desc' as OrderByDirection,
|
||||
};
|
||||
const loading = '게시물을 불러오는 중입니다...';
|
||||
import { useBoardList } from '@/composables/useBoardList';
|
||||
|
||||
// destructure only the things you actually need
|
||||
const {
|
||||
isLoading,
|
||||
loadingMessage,
|
||||
compdata,
|
||||
currentItems,
|
||||
currentPage,
|
||||
pageNumbers,
|
||||
selectedSort,
|
||||
sortOptions,
|
||||
userRole,
|
||||
totalPages,
|
||||
selectedItems,
|
||||
showSelectBoxes,
|
||||
onToggleSelect,
|
||||
onDeleteSelected,
|
||||
onGoToPage,
|
||||
onPrevPage,
|
||||
onNextPage,
|
||||
} = useBoardList(currentCollection, {
|
||||
title: compData.title,
|
||||
itemsPerPage: compData.itemsPerPage,
|
||||
defaultSort: compData.defaultSort,
|
||||
access: access,
|
||||
loadingMessage: loading,
|
||||
});
|
||||
|
||||
// no more fetchBoardsAndUpdateItems or onBeforeMount
|
||||
</script>
|
||||
22
bobu/app/pages/wadiz/success.vue
Normal file
22
bobu/app/pages/wadiz/success.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<section class="bg-white dark:bg-gray-900 py-16 px-6">
|
||||
<div class="max-w-2xl mx-auto text-center">
|
||||
<h1
|
||||
class="text-2xl sm:text-3xl font-bold text-gray-800 dark:text-white mb-6"
|
||||
>
|
||||
예약이 완료되었습니다. 감사합니다.
|
||||
</h1>
|
||||
<p class="text-lg text-gray-700 dark:text-gray-300 leading-relaxed">
|
||||
정선의 자연, 그리고 보부의 하루에 함께해주셔서 진심으로 감사합니다.
|
||||
여러분의 한 걸음이 저희에겐 큰 의미였습니다.<br /><br />
|
||||
자연 속에서의 쉼, 차분히 준비해두겠습니다.<br />
|
||||
<span class="font-semibold text-indigo-600 dark:text-indigo-400"
|
||||
>곧, 정선에서 뵙겠습니다.</span
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
console.log('Success page mounted!');
|
||||
</script>
|
||||
16
bobu/app/pages/wadiz/upload.vue
Normal file
16
bobu/app/pages/wadiz/upload.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<app-upload-wadiz-form
|
||||
v-if="!isSuccess"
|
||||
:isEdit="false"
|
||||
@success="isSuccess = true"
|
||||
/>
|
||||
<app-wadiz-success v-if="isSuccess" />
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const isSuccess = ref(false);
|
||||
import AppUploadWadizForm from '@/components/boards/wadiz/UploadWadizForm.vue';
|
||||
import AppWadizSuccess from '@/components/boards/wadiz/WadizSuccess.vue';
|
||||
</script>
|
||||
9
bobu/app/pages/wadizsub.vue
Normal file
9
bobu/app/pages/wadizsub.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- <app-wadiz /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AppWadiz from '~/components/WadizWelcome.vue';
|
||||
</script>
|
||||
@@ -36,6 +36,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
|
||||
// Collections
|
||||
const usersCollection = collection(db, 'users');
|
||||
const wadizesCollection = collection(db, 'wadizes');
|
||||
const faqboardsCollection = collection(db, 'faqboards');
|
||||
const countersCollection = collection(db, 'counters');
|
||||
const attendsCollection = collection(db, 'attends');
|
||||
@@ -76,6 +77,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
||||
functions,
|
||||
analytics,
|
||||
usersCollection,
|
||||
wadizesCollection,
|
||||
faqboardsCollection,
|
||||
countersCollection,
|
||||
attendsCollection,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
ProgramCategory,
|
||||
VideoProvider,
|
||||
} from '../data/config';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// Management Page - Link to firebase/functions/src/types/boardItem.ts
|
||||
// If you change , change up there too *******
|
||||
@@ -124,6 +125,22 @@ export type BoardItem = {
|
||||
ishidden?: boolean; // depriciated, we can use this for later admin control
|
||||
thumbnail?: ImageItem; //Leave it for previous data structure
|
||||
};
|
||||
export interface WadizBoard extends BoardItem {
|
||||
name: string;
|
||||
paymentId: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
emergencyPhone: string;
|
||||
scheduleStart?: string | Date;
|
||||
scheduleEnd?: string | Date;
|
||||
attending: 'yes' | 'no';
|
||||
altName?: string;
|
||||
altPhone?: string;
|
||||
companions?: string;
|
||||
healthNotes?: string;
|
||||
remarks?: string;
|
||||
}
|
||||
// Extended Board Types
|
||||
export interface ProjectBoard extends BoardItem {
|
||||
subtitle: string;
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface FirebasePlugin {
|
||||
functions: Functions;
|
||||
analytics: Analytics;
|
||||
usersCollection: CollectionReference;
|
||||
wadizesCollection: CollectionReference;
|
||||
faqboardsCollection: CollectionReference;
|
||||
countersCollection: CollectionReference;
|
||||
attendsCollection: CollectionReference;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const SESSION_URL = 'https://createsession-d4sni42fjq-du.a.run.app';
|
||||
const LOGOUT_URL = 'https://logout-d4sni42fjq-du.a.run.app';
|
||||
const SESSION_URL = 'https://createsession-edvvp3hbnq-du.a.run.app';
|
||||
const LOGOUT_URL = 'https://logout-edvvp3hbnq-du.a.run.app';
|
||||
|
||||
/**
|
||||
* Call Firebase Function to create session cookie.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { BoardAccessMode } from '~/types';
|
||||
|
||||
const FUNCTION_BASE = 'https://countboards-d4sni42fjq-du.a.run.app';
|
||||
const FUNCTION_BASE = 'https://countboards-edvvp3hbnq-du.a.run.app';
|
||||
|
||||
export async function fetchCountsFromFunction(
|
||||
collection: string,
|
||||
access: BoardAccessMode = 'public'
|
||||
): Promise<number> {
|
||||
return await $fetch<{ count: number }>(`${FUNCTION_BASE}/countBoards`, {
|
||||
return await $fetch<{ count: number }>(`${FUNCTION_BASE}`, {
|
||||
method: 'POST', // ✅ explicitly typed string
|
||||
body: { collection, access }, // ✅ valid JSON object
|
||||
credentials: access !== 'public' ? 'include' : undefined, // ✅ conditional credentials
|
||||
|
||||
@@ -15,7 +15,7 @@ interface FetchBoardsParams {
|
||||
pageToken?: string;
|
||||
}
|
||||
|
||||
const FUNCTION_BASE = 'https://fetchboards-d4sni42fjq-du.a.run.app';
|
||||
const FUNCTION_BASE = 'https://fetchboards-edvvp3hbnq-du.a.run.app';
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
export async function fetchBoardsFromFunction<T extends BoardItem>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const VERIFY_URL = 'https://verifysession-d4sni42fjq-du.a.run.app';
|
||||
const VERIFY_URL = 'https://verifysession-edvvp3hbnq-du.a.run.app';
|
||||
|
||||
export type VerifiedSession = {
|
||||
uid: string;
|
||||
|
||||
27
bobu/package-lock.json
generated
27
bobu/package-lock.json
generated
@@ -22,8 +22,10 @@
|
||||
"@vee-validate/nuxt": "^4.15.0",
|
||||
"@vee-validate/rules": "^4.15.0",
|
||||
"@vesp/nuxt-fontawesome": "^1.2.1",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"ckeditor5": "^45.0.0",
|
||||
"ckeditor5-premium-features": "^45.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"firebase": "^11.8.1",
|
||||
"firebase-functions": "^6.3.2",
|
||||
"install": "^0.13.0",
|
||||
@@ -12190,6 +12192,21 @@
|
||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vuepic/vue-datepicker": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz",
|
||||
"integrity": "sha512-uHh78mVBXCEjam1uVfTzZ/HkyDwut/H6b2djSN9YTF+l/EA+XONfdCnOVSi1g+qVGSy65DcQAwyBNidAssnudQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"date-fns": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
@@ -13939,6 +13956,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
}
|
||||
},
|
||||
"node_modules/db0": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/db0/-/db0-0.3.1.tgz",
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
"@vee-validate/nuxt": "^4.15.0",
|
||||
"@vee-validate/rules": "^4.15.0",
|
||||
"@vesp/nuxt-fontawesome": "^1.2.1",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"ckeditor5": "^45.0.0",
|
||||
"ckeditor5-premium-features": "^45.0.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"firebase": "^11.8.1",
|
||||
"firebase-functions": "^6.3.2",
|
||||
"install": "^0.13.0",
|
||||
|
||||
BIN
bobu/public/assets/img/logo/Bobu_Red.webp
Normal file
BIN
bobu/public/assets/img/logo/Bobu_Red.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
BIN
bobu/public/assets/img/logo/Bobu_White.webp
Normal file
BIN
bobu/public/assets/img/logo/Bobu_White.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
Reference in New Issue
Block a user