feat: 초기 설정 및 화이트 테마 적용, CI/CD 워크플로우 추가
This commit is contained in:
parent
6372c699e6
commit
30b4125af7
41
.gitea/workflows/deploy.yml
Normal file
41
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Build and Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-front:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install front dependencies
|
||||||
|
run: cd front && npm ci
|
||||||
|
|
||||||
|
- name: Build front
|
||||||
|
run: cd front && npm run build
|
||||||
|
|
||||||
|
- name: Deploy front
|
||||||
|
run: rsync -a --delete front/dist/ /home/hyoseung/coupon/dist/
|
||||||
|
|
||||||
|
deploy-back:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install back dependencies
|
||||||
|
run: cd back && npm ci
|
||||||
|
|
||||||
|
- name: Build back
|
||||||
|
run: cd back && npm run build
|
||||||
|
|
||||||
|
- name: Deploy back
|
||||||
|
run: |
|
||||||
|
rsync -a --delete back/dist/ /home/hyoseung/coupon/back/dist/
|
||||||
|
cd /home/hyoseung/coupon/back
|
||||||
|
pm2 restart coupon-back || pm2 start ecosystem.config.js
|
||||||
|
pm2 save
|
||||||
@ -32,21 +32,22 @@ const successCount = computed(() => store.results.filter((r) => r.status === 'su
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.redeem-results { display: flex; flex-direction: column; gap: 10px; }
|
.redeem-results { display: flex; flex-direction: column; gap: 10px; }
|
||||||
.results-header { display: flex; justify-content: space-between; align-items: center; }
|
.results-header { display: flex; justify-content: space-between; align-items: center; }
|
||||||
.label { color: #94a3b8; font-size: 0.85rem; }
|
.label { color: #64748b; font-size: 0.85rem; }
|
||||||
.summary { color: #60a5fa; font-size: 0.85rem; font-weight: 600; }
|
.summary { color: #3b82f6; font-size: 0.85rem; font-weight: 600; }
|
||||||
.result-list { display: flex; flex-direction: column; gap: 6px; }
|
.result-list { display: flex; flex-direction: column; gap: 6px; }
|
||||||
.result-row {
|
.result-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: #0f172a;
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.result-info { display: flex; flex-direction: column; gap: 2px; }
|
.result-info { display: flex; flex-direction: column; gap: 2px; }
|
||||||
.result-name { color: #cbd5e1; font-size: 0.9rem; }
|
.result-name { color: #1e293b; font-size: 0.9rem; }
|
||||||
.result-code { color: #475569; font-family: monospace; font-size: 0.78rem; }
|
.result-code { color: #64748b; font-family: monospace; font-size: 0.78rem; }
|
||||||
.badge {
|
.badge {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@ -54,8 +55,8 @@ const successCount = computed(() => store.results.filter((r) => r.status === 'su
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.badge.success { background: #166534; color: #4ade80; }
|
.badge.success { background: #dcfce7; color: #16a34a; }
|
||||||
.badge.error { background: #7f1d1d; color: #f87171; }
|
.badge.error { background: #fee2e2; color: #dc2626; }
|
||||||
.badge.pending { background: #1e3a5f; color: #93c5fd; }
|
.badge.pending { background: #eff6ff; color: #3b82f6; }
|
||||||
.empty-result p { color: #475569; font-size: 0.9rem; margin: 0; text-align: center; }
|
.empty-result p { color: #94a3b8; font-size: 0.9rem; margin: 0; text-align: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -50,20 +50,20 @@ async function add() {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.coupon-manager { display: flex; flex-direction: column; gap: 14px; }
|
.coupon-manager { display: flex; flex-direction: column; gap: 14px; }
|
||||||
.manager-header { display: flex; align-items: center; justify-content: space-between; }
|
.manager-header { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.manager-header h3 { margin: 0; color: #f1f5f9; font-size: 1rem; }
|
.manager-header h3 { margin: 0; color: #1e293b; font-size: 1rem; }
|
||||||
.active-count { background: #1e3a5f; color: #60a5fa; padding: 3px 10px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
|
.active-count { background: #eff6ff; color: #3b82f6; padding: 3px 10px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; }
|
||||||
.add-form { display: flex; gap: 8px; flex-wrap: wrap; }
|
.add-form { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||||
.input {
|
.input {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: #0f172a;
|
background: #f8fafc;
|
||||||
border: 1px solid #334155;
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: #f1f5f9;
|
color: #1e293b;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
.input:focus { outline: none; border-color: #60a5fa; }
|
.input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1); }
|
||||||
.code-input { font-family: monospace; }
|
.code-input { font-family: monospace; }
|
||||||
.btn-add {
|
.btn-add {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
@ -84,14 +84,15 @@ async function add() {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
background: #0f172a;
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-left: 3px solid #22c55e;
|
border-left: 3px solid #22c55e;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
.coupon-item.expired { border-left-color: #7c3aed; opacity: 0.5; }
|
.coupon-item.expired { border-left-color: #a855f7; opacity: 0.6; }
|
||||||
.coupon-info { display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
.coupon-info { display: flex; flex-direction: column; gap: 2px; flex: 1; }
|
||||||
.coupon-code { color: #60a5fa; font-family: monospace; font-size: 0.9rem; }
|
.coupon-code { color: #3b82f6; font-family: monospace; font-size: 0.9rem; }
|
||||||
.coupon-actions { display: flex; gap: 6px; align-items: center; }
|
.coupon-actions { display: flex; gap: 6px; align-items: center; }
|
||||||
.btn-toggle {
|
.btn-toggle {
|
||||||
padding: 3px 10px;
|
padding: 3px 10px;
|
||||||
@ -101,10 +102,10 @@ async function add() {
|
|||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.btn-toggle.on { background: #166534; color: #4ade80; }
|
.btn-toggle.on { background: #dcfce7; color: #16a34a; }
|
||||||
.btn-toggle.off { background: #374151; color: #9ca3af; }
|
.btn-toggle.off { background: #f1f5f9; color: #64748b; }
|
||||||
.badge { padding: 3px 10px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; }
|
.badge { padding: 3px 10px; border-radius: 20px; font-size: 0.78rem; font-weight: 600; }
|
||||||
.badge-active { background: #166534; color: #4ade80; }
|
.badge-active { background: #dcfce7; color: #16a34a; }
|
||||||
.badge-expired { background: #3b0764; color: #c084fc; }
|
.badge-expired { background: #f3e8ff; color: #9333ea; }
|
||||||
.empty { color: #475569; font-size: 0.9rem; margin: 0; text-align: center; }
|
.empty { color: #94a3b8; font-size: 0.9rem; margin: 0; text-align: center; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -50,7 +50,7 @@ function formatDate(dt: string) {
|
|||||||
}
|
}
|
||||||
.history-header h3 {
|
.history-header h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #f1f5f9;
|
color: #1e293b;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
.count {
|
.count {
|
||||||
@ -66,16 +66,17 @@ function formatDate(dt: string) {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
.list::-webkit-scrollbar { width: 4px; }
|
.list::-webkit-scrollbar { width: 4px; }
|
||||||
.list::-webkit-scrollbar-track { background: #0f172a; }
|
.list::-webkit-scrollbar-track { background: #f1f5f9; }
|
||||||
.list::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
|
.list::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
background: #0f172a;
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border-left: 3px solid #334155;
|
border-left: 3px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
.item.success { border-left-color: #22c55e; }
|
.item.success { border-left-color: #22c55e; }
|
||||||
.item.error { border-left-color: #ef4444; }
|
.item.error { border-left-color: #ef4444; }
|
||||||
@ -86,11 +87,11 @@ function formatDate(dt: string) {
|
|||||||
}
|
}
|
||||||
.code {
|
.code {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
color: #cbd5e1;
|
color: #334155;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
.time {
|
.time {
|
||||||
color: #475569;
|
color: #94a3b8;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
.badge {
|
.badge {
|
||||||
@ -100,6 +101,6 @@ function formatDate(dt: string) {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.badge.success { background: #166534; color: #4ade80; }
|
.badge.success { background: #dcfce7; color: #16a34a; }
|
||||||
.badge.error { background: #7f1d1d; color: #f87171; }
|
.badge.error { background: #fee2e2; color: #dc2626; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="search-row">
|
<div class="search-row">
|
||||||
<input
|
<input
|
||||||
|
ref="fidInputRef"
|
||||||
v-model="fidInput"
|
v-model="fidInput"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="FID (플레이어 ID) 입력"
|
placeholder="FID (플레이어 ID) 입력"
|
||||||
@ -66,7 +67,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||||
import { useWosStore } from '../stores/wos.store';
|
import { useWosStore } from '../stores/wos.store';
|
||||||
import UserCard from '../components/UserCard.vue';
|
import UserCard from '../components/UserCard.vue';
|
||||||
import CouponInput from '../components/CouponInput.vue';
|
import CouponInput from '../components/CouponInput.vue';
|
||||||
@ -74,6 +75,7 @@ import CouponManager from '../components/CouponManager.vue';
|
|||||||
|
|
||||||
const store = useWosStore();
|
const store = useWosStore();
|
||||||
const fidInput = ref('');
|
const fidInput = ref('');
|
||||||
|
const fidInputRef = ref<HTMLInputElement | null>(null);
|
||||||
const userSearchQuery = ref('');
|
const userSearchQuery = ref('');
|
||||||
|
|
||||||
const filteredUsers = computed(() => {
|
const filteredUsers = computed(() => {
|
||||||
@ -93,6 +95,8 @@ onMounted(async () => {
|
|||||||
await store.searchPlayer(savedFid);
|
await store.searchPlayer(savedFid);
|
||||||
store.loadSavedUsers();
|
store.loadSavedUsers();
|
||||||
}
|
}
|
||||||
|
await nextTick();
|
||||||
|
fidInputRef.value?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function searchPlayer() {
|
async function searchPlayer() {
|
||||||
@ -101,6 +105,8 @@ async function searchPlayer() {
|
|||||||
localStorage.setItem('wos_fid', fid);
|
localStorage.setItem('wos_fid', fid);
|
||||||
await store.searchPlayer(fid);
|
await store.searchPlayer(fid);
|
||||||
store.loadSavedUsers();
|
store.loadSavedUsers();
|
||||||
|
await nextTick();
|
||||||
|
fidInputRef.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function selectUser(fid: string) {
|
async function selectUser(fid: string) {
|
||||||
@ -108,6 +114,8 @@ async function selectUser(fid: string) {
|
|||||||
localStorage.setItem('wos_fid', fid);
|
localStorage.setItem('wos_fid', fid);
|
||||||
await store.searchPlayer(fid);
|
await store.searchPlayer(fid);
|
||||||
store.loadSavedUsers();
|
store.loadSavedUsers();
|
||||||
|
await nextTick();
|
||||||
|
fidInputRef.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onImgErr(e: Event) {
|
function onImgErr(e: Event) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user