coupon/front/src/components/CouponInput.vue
hyoseung930 1b43b874a1 쿠폰 자동 입력 사이트 작성
완료됐습니다. 변경 내용 요약:

---

## 변경 사항

### 버그 수정
- TypeORM `upsert` → MySQL 호환 `findOne + update/save` 방식으로 수정 → 500 에러 해결

### 신규 기능

**쿠폰 관리 (관리자)**
- **https://coupon.wageulwageul.com/admin** 에서 쿠폰 추가/삭제/활성화 토글
- 쿠폰 코드 + 이름 저장 (예: `ABC123` / `신년 이벤트`)

**자동 전체 지급 (사용자)**
- FID 입력 → 조회 → ** 전체 쿠폰 자동 지급** 버튼 클릭
- DB에 등록된 활성 쿠폰을 전부 자동으로 시도
- 결과(쿠폰명 + 코드 + 성공/실패)가 바로 표시되고 DB에 영구 저장
- 이력은 FID별로 조회 시 자동 로드

**사용 흐름:**
1. `/admin` 에서 쿠폰 등록
2. 사용자는 `/` 에서 FID 입력 → 버튼 한 번으로 모든 쿠폰 자동 지급
2026-04-16 18:03:45 +09:00

62 lines
2.1 KiB
Vue

<template>
<div class="redeem-results" v-if="store.results.length > 0">
<div class="results-header">
<span class="label">이번 지급 결과</span>
<span class="summary">
성공 {{ successCount }}개 / 전체 {{ store.results.length }}개
</span>
</div>
<div class="result-list">
<div v-for="(r, i) in store.results" :key="i" class="result-row">
<div class="result-info">
<span class="result-name">{{ r.name }}</span>
<span class="result-code">{{ r.code }}</span>
</div>
<span class="badge" :class="r.status">{{ r.message }}</span>
</div>
</div>
</div>
<div class="empty-result" v-else-if="store.status === 'idle' && store.player">
<p>쿠폰이 없거나 모두 지급됐습니다.</p>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useWosStore } from '../stores/wos.store';
const store = useWosStore();
const successCount = computed(() => store.results.filter((r) => r.status === 'success').length);
</script>
<style scoped>
.redeem-results { display: flex; flex-direction: column; gap: 10px; }
.results-header { display: flex; justify-content: space-between; align-items: center; }
.label { color: #94a3b8; font-size: 0.85rem; }
.summary { color: #60a5fa; font-size: 0.85rem; font-weight: 600; }
.result-list { display: flex; flex-direction: column; gap: 6px; }
.result-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: #0f172a;
border-radius: 8px;
gap: 8px;
}
.result-info { display: flex; flex-direction: column; gap: 2px; }
.result-name { color: #cbd5e1; font-size: 0.9rem; }
.result-code { color: #475569; font-family: monospace; font-size: 0.78rem; }
.badge {
padding: 3px 10px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 600;
white-space: nowrap;
}
.badge.success { background: #166534; color: #4ade80; }
.badge.error { background: #7f1d1d; color: #f87171; }
.badge.pending { background: #1e3a5f; color: #93c5fd; }
.empty-result p { color: #475569; font-size: 0.9rem; margin: 0; text-align: center; }
</style>