From 608a904768b4a2ec8af8841e1b19c112ed629192 Mon Sep 17 00:00:00 2001 From: hyoseung930 <35983843+hyoseung930@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:06:00 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BF=A0=ED=8F=B0=20=EC=9E=90=EB=8F=99=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배포 완료됐습니다. 이제 FID 입력란 아래에 **최근 유저** 칩이 표시됩니다. 한 번 조회한 사람은 아바타 + 닉네임 칩으로 저장되어, 다음번엔 클릭 한 번으로 바로 쿠폰을 받을 수 있습니다. --- back/src/wos/wos.controller.ts | 5 ++++ back/src/wos/wos.service.ts | 4 +++ front/src/stores/wos.store.ts | 15 ++++++++++ front/src/views/HomeView.vue | 51 +++++++++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/back/src/wos/wos.controller.ts b/back/src/wos/wos.controller.ts index 5b94db8..b47bfcd 100644 --- a/back/src/wos/wos.controller.ts +++ b/back/src/wos/wos.controller.ts @@ -17,6 +17,11 @@ export class WosController { return this.wosService.redeemAll(body.fid); } + @Get('users') + async listUsers() { + return this.wosService.listUsers(); + } + @Get('history/:fid') async getHistory(@Param('fid') fid: string) { return this.wosService.getHistory(fid); diff --git a/back/src/wos/wos.service.ts b/back/src/wos/wos.service.ts index f50cb08..198324b 100644 --- a/back/src/wos/wos.service.ts +++ b/back/src/wos/wos.service.ts @@ -154,6 +154,10 @@ export class WosService { }); } + async listUsers() { + return this.wosUserRepo.find({ order: { created_at: 'DESC' } }); + } + async listCoupons() { return this.wosCouponRepo.find({ order: { created_at: 'DESC' } }); } diff --git a/front/src/stores/wos.store.ts b/front/src/stores/wos.store.ts index c47a571..1084a6d 100644 --- a/front/src/stores/wos.store.ts +++ b/front/src/stores/wos.store.ts @@ -30,6 +30,13 @@ export interface Coupon { created_at: string; } +export interface SavedUser { + fid: string; + nickname: string; + avatar_url: string; + created_at: string; +} + export const useWosStore = defineStore('wos', { state: () => ({ fid: '', @@ -37,6 +44,7 @@ export const useWosStore = defineStore('wos', { results: [] as RedeemResult[], history: [] as HistoryItem[], coupons: [] as Coupon[], + savedUsers: [] as SavedUser[], status: 'idle' as 'idle' | 'loading' | 'success' | 'error', errorMsg: '', }), @@ -78,6 +86,13 @@ export const useWosStore = defineStore('wos', { } catch {} }, + async loadSavedUsers() { + try { + const { data } = await axios.get('/api/users'); + this.savedUsers = data; + } catch {} + }, + async loadCoupons() { try { const { data } = await axios.get('/api/coupons'); diff --git a/front/src/views/HomeView.vue b/front/src/views/HomeView.vue index 3e52f47..c3a12d4 100644 --- a/front/src/views/HomeView.vue +++ b/front/src/views/HomeView.vue @@ -21,6 +21,22 @@

{{ store.errorMsg }}

+ +
+ 최근 유저 +
+ +
+
@@ -52,11 +68,25 @@ import CouponManager from '../components/CouponManager.vue'; const store = useWosStore(); const fidInput = ref(''); -onMounted(() => store.loadCoupons()); +onMounted(() => { + store.loadCoupons(); + store.loadSavedUsers(); +}); async function searchPlayer() { if (!fidInput.value.trim()) return; await store.searchPlayer(fidInput.value.trim()); + store.loadSavedUsers(); +} + +async function selectUser(fid: string) { + fidInput.value = fid; + await store.searchPlayer(fid); + store.loadSavedUsers(); +} + +function onImgErr(e: Event) { + (e.target as HTMLImageElement).style.display = 'none'; } @@ -107,4 +137,23 @@ header h1 { font-size: 1.9rem; color: #f1f5f9; margin: 0 0 8px; } .btn-primary:hover:not(:disabled) { background: #2563eb; } .error-msg { color: #f87171; font-size: 0.9rem; margin: 8px 0 0; } .empty-history { color: #475569; font-size: 0.9rem; margin: 0; text-align: center; } +.saved-users { margin-top: 14px; } +.saved-label { color: #64748b; font-size: 0.8rem; display: block; margin-bottom: 8px; } +.chips { display: flex; flex-wrap: wrap; gap: 8px; } +.chip { + display: flex; + align-items: center; + gap: 6px; + padding: 5px 12px 5px 6px; + background: #0f172a; + border: 1px solid #334155; + border-radius: 20px; + color: #cbd5e1; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.15s; +} +.chip:hover { border-color: #60a5fa; color: #f1f5f9; } +.chip.active { border-color: #3b82f6; background: #1e3a5f; color: #93c5fd; } +.chip-avatar { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; }