|
@@ -197,6 +197,43 @@
|
|
|
background: rgba(255, 255, 255, 0.2);
|
|
|
border-color: rgba(255, 255, 255, 0.4);
|
|
|
}
|
|
|
+
|
|
|
+ /* Toast Animation */
|
|
|
+ .toast-enter-active,
|
|
|
+ .toast-leave-active {
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .toast-enter-from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .toast-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(100%);
|
|
|
+ }
|
|
|
+
|
|
|
+ .toast-enter-to,
|
|
|
+ .toast-leave-from {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ .animate-slide-in {
|
|
|
+ animation: slideIn 0.3s ease-out;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes slideIn {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(100%);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
</style>
|
|
|
</head>
|
|
|
<body
|
|
@@ -247,154 +284,121 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 relative z-10">
|
|
|
- <!-- Left: Reward Management -->
|
|
|
- <div class="space-y-8">
|
|
|
- <!-- Reward Management -->
|
|
|
- <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
- <div class="p-6">
|
|
|
- <div class="flex justify-between items-center mb-6">
|
|
|
- <h2 class="text-2xl font-bold text-white">
|
|
|
- <i class="fas fa-gift text-purple-300 mr-3"></i>
|
|
|
- Reward Management
|
|
|
- </h2>
|
|
|
- <div class="badge badge-primary badge-lg animate-pulse-slow">
|
|
|
- {{ waitAddRewardList.length }} Types
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
+ <div class="space-y-8 relative z-10">
|
|
|
+ <!-- Step 1: Address Management -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex items-center mb-6">
|
|
|
<div
|
|
|
- v-if="waitAddRewardList.length === 0"
|
|
|
- class="text-center py-12"
|
|
|
+ class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
|
|
|
>
|
|
|
- <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
|
|
|
- <p class="text-purple-200 text-lg">No rewards added yet</p>
|
|
|
- <p class="text-purple-300 text-sm mt-2">
|
|
|
- Click the button below to add rewards
|
|
|
- </p>
|
|
|
+ 1
|
|
|
</div>
|
|
|
+ <h2 class="text-3xl font-bold text-white">
|
|
|
+ <i class="fas fa-users text-blue-300 mr-3"></i>
|
|
|
+ Step 1: Add Wallet Addresses
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
|
|
|
- <div
|
|
|
- v-else
|
|
|
- class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
|
|
|
- >
|
|
|
- <div
|
|
|
- v-for="reward in waitAddRewardList"
|
|
|
- :key="reward.id"
|
|
|
- class="reward-item rounded-xl p-4 border border-white/20"
|
|
|
- >
|
|
|
- <div class="flex justify-between items-center">
|
|
|
- <div class="flex items-center space-x-4">
|
|
|
- <div class="w-12 h-12 icon-container backdrop-blur-sm">
|
|
|
- <img
|
|
|
- :src="getItemIcon(reward.id)"
|
|
|
- :alt="getItemName(reward.id)"
|
|
|
- class="w-8 h-8 item-icon"
|
|
|
- @error="handleImageError"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <div class="font-semibold text-lg">
|
|
|
- {{ getItemName(reward.id) }}
|
|
|
- </div>
|
|
|
- <div class="text-sm opacity-80">
|
|
|
- {{ reward.count }} per address
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="flex items-center space-x-3">
|
|
|
- <input
|
|
|
- type="number"
|
|
|
- v-model="reward.count"
|
|
|
- min="1"
|
|
|
- class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
|
|
|
- @change="updateRewardCount(reward.id, reward.count)"
|
|
|
- />
|
|
|
- <button
|
|
|
- class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
- @click="removeWaitReward(reward.id)"
|
|
|
- >
|
|
|
- <i class="fas fa-times"></i>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
|
+ <!-- Address Input -->
|
|
|
+ <div>
|
|
|
+ <div class="form-control">
|
|
|
+ <label class="label">
|
|
|
+ <span class="label-text font-semibold text-white text-lg"
|
|
|
+ >Wallet Address List</span
|
|
|
+ >
|
|
|
+ <span class="label-text-alt text-purple-300"
|
|
|
+ >{{ addressList.length }} addresses</span
|
|
|
+ >
|
|
|
+ </label>
|
|
|
+ <textarea
|
|
|
+ v-model="waitInputAddresses"
|
|
|
+ rows="10"
|
|
|
+ placeholder="Enter wallet addresses, one per line Example: 0x1234567890abcdef... 0xabcdef1234567890..."
|
|
|
+ class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
+ @input="parseAddresses"
|
|
|
+ ></textarea>
|
|
|
+ <label class="label">
|
|
|
+ <span class="label-text-alt text-purple-200"
|
|
|
+ >Support batch paste, one address per line</span
|
|
|
+ >
|
|
|
+ </label>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="text-center mt-6">
|
|
|
- <button
|
|
|
- class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
|
|
|
- @click="showAddRewardDialog = true"
|
|
|
- >
|
|
|
- <i class="fas fa-plus mr-2"></i>
|
|
|
- Add Reward
|
|
|
- </button>
|
|
|
+ <!-- Quick Actions -->
|
|
|
+ <div class="flex flex-wrap gap-2 mt-4">
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="clearAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-trash mr-1"></i> Clear
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="exportAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-download mr-1"></i> Export
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
+ @click="importAddresses"
|
|
|
+ >
|
|
|
+ <i class="fas fa-upload mr-1"></i> Import
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- Send Preview -->
|
|
|
- <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
- <div class="p-6">
|
|
|
- <h2 class="text-2xl font-bold text-white mb-6">
|
|
|
- <i class="fas fa-chart-line text-blue-300 mr-3"></i>
|
|
|
- Send Preview
|
|
|
- </h2>
|
|
|
|
|
|
- <!-- Statistics -->
|
|
|
- <div class="grid grid-cols-3 gap-4 mb-8">
|
|
|
- <div class="stat-card rounded-xl p-4 text-center">
|
|
|
- <div class="text-3xl font-bold text-white mb-1">
|
|
|
- {{ addressList.length }}
|
|
|
- </div>
|
|
|
- <div class="text-sm text-purple-200">Addresses</div>
|
|
|
- </div>
|
|
|
- <div class="stat-card rounded-xl p-4 text-center">
|
|
|
- <div class="text-3xl font-bold text-white mb-1">
|
|
|
- {{ totalRewardCount }}
|
|
|
+ <!-- Address List -->
|
|
|
+ <div>
|
|
|
+ <div class="flex justify-between items-center mb-4">
|
|
|
+ <h3 class="text-xl font-semibold text-white">
|
|
|
+ <i class="fas fa-list text-orange-300 mr-2"></i>
|
|
|
+ Address List
|
|
|
+ </h3>
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ v-model="searchAddress"
|
|
|
+ placeholder="Search addresses..."
|
|
|
+ class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
+ />
|
|
|
+ <div class="badge badge-outline border-white/30 text-white">
|
|
|
+ {{ filteredAddresses.length }}/{{ addressList.length }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- <div class="text-sm text-purple-200">Rewards</div>
|
|
|
</div>
|
|
|
- <div class="stat-card rounded-xl p-4 text-center">
|
|
|
- <div class="text-3xl font-bold text-white mb-1">
|
|
|
- {{ totalRewardCount * addressList.length }}
|
|
|
- </div>
|
|
|
- <div class="text-sm text-purple-200">Total</div>
|
|
|
+
|
|
|
+ <div v-if="addressList.length === 0" class="text-center py-8">
|
|
|
+ <div class="text-6xl mb-4 animate-bounce-slow">👤</div>
|
|
|
+ <p class="text-purple-200 text-lg">No addresses yet</p>
|
|
|
+ <p class="text-purple-300 text-sm mt-2">
|
|
|
+ Please enter addresses on the left
|
|
|
+ </p>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- Item Summary -->
|
|
|
- <div v-if="waitAddRewardList.length > 0">
|
|
|
- <h3 class="text-xl font-semibold text-white mb-4">
|
|
|
- Item Summary
|
|
|
- </h3>
|
|
|
- <div class="space-y-3 max-h-60 overflow-y-auto scrollbar-thin">
|
|
|
+ <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
|
|
|
<div
|
|
|
- v-for="reward in waitAddRewardList"
|
|
|
- :key="reward.id"
|
|
|
- class="flex justify-between items-center p-3 bg-white/10 rounded-lg backdrop-blur-sm"
|
|
|
+ v-for="(address, index) in filteredAddresses"
|
|
|
+ :key="index"
|
|
|
+ class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
|
|
|
>
|
|
|
- <div class="flex items-center space-x-3">
|
|
|
- <div class="w-8 h-8 icon-container">
|
|
|
- <img
|
|
|
- :src="getItemIcon(reward.id)"
|
|
|
- :alt="getItemName(reward.id)"
|
|
|
- class="w-6 h-6 item-icon"
|
|
|
- @error="handleImageError"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <span class="font-medium text-white"
|
|
|
- >{{ getItemName(reward.id) }}</span
|
|
|
- >
|
|
|
- </div>
|
|
|
- <div class="flex items-center space-x-2">
|
|
|
- <span class="text-sm text-purple-200"
|
|
|
- >{{ reward.count }} × {{ addressList.length }}</span
|
|
|
- >
|
|
|
- <span class="font-bold text-yellow-300"
|
|
|
- >= {{ reward.count * addressList.length }}</span
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <div
|
|
|
+ class="w-8 h-8 bg-gradient-to-r from-purple-400 to-pink-400 rounded-full flex items-center justify-center text-xs font-bold text-white"
|
|
|
>
|
|
|
+ {{ index + 1 }}
|
|
|
+ </div>
|
|
|
+ <div class="font-mono text-sm text-white">
|
|
|
+ {{ address }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ <button
|
|
|
+ class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
+ @click="removeAddress(index)"
|
|
|
+ >
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -402,131 +406,209 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Right: Address Management -->
|
|
|
- <div class="space-y-8">
|
|
|
- <!-- Address Input -->
|
|
|
- <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
- <div class="p-6">
|
|
|
- <h2 class="text-2xl font-bold text-white mb-6">
|
|
|
- <i class="fas fa-users text-green-300 mr-3"></i>
|
|
|
- Address Management
|
|
|
+ <!-- Step 2: Reward Management -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex items-center mb-6">
|
|
|
+ <div
|
|
|
+ class="w-12 h-12 bg-gradient-to-r from-purple-500 to-pink-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
|
|
|
+ >
|
|
|
+ 2
|
|
|
+ </div>
|
|
|
+ <h2 class="text-3xl font-bold text-white">
|
|
|
+ <i class="fas fa-gift text-purple-300 mr-3"></i>
|
|
|
+ Step 2: Select Rewards
|
|
|
</h2>
|
|
|
-
|
|
|
- <div class="form-control">
|
|
|
- <label class="label">
|
|
|
- <span class="label-text font-medium text-white"
|
|
|
- >Wallet Address List</span
|
|
|
- >
|
|
|
- <span class="label-text-alt text-purple-300"
|
|
|
- >{{ addressList.length }} addresses</span
|
|
|
- >
|
|
|
- </label>
|
|
|
- <textarea
|
|
|
- v-model="waitInputAddresses"
|
|
|
- rows="8"
|
|
|
- placeholder="Enter wallet addresses, one per line Example: 0x1234567890abcdef... 0xabcdef1234567890..."
|
|
|
- class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
- @input="parseAddresses"
|
|
|
- ></textarea>
|
|
|
- <label class="label">
|
|
|
- <span class="label-text-alt text-purple-200"
|
|
|
- >Support batch paste, one address per line</span
|
|
|
- >
|
|
|
- </label>
|
|
|
+ <div class="badge badge-primary badge-lg animate-pulse-slow ml-4">
|
|
|
+ {{ waitAddRewardList.length }} Types
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- Quick Actions -->
|
|
|
- <div class="flex flex-wrap gap-2 mt-6">
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
- @click="clearAddresses"
|
|
|
- >
|
|
|
- <i class="fas fa-trash mr-1"></i> Clear
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
- @click="exportAddresses"
|
|
|
- >
|
|
|
- <i class="fas fa-download mr-1"></i> Export
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
|
|
|
- @click="importAddresses"
|
|
|
- >
|
|
|
- <i class="fas fa-upload mr-1"></i> Import
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+ <div
|
|
|
+ v-if="waitAddRewardList.length === 0"
|
|
|
+ class="text-center py-12"
|
|
|
+ >
|
|
|
+ <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
|
|
|
+ <p class="text-purple-200 text-lg">No rewards added yet</p>
|
|
|
+ <p class="text-purple-300 text-sm mt-2">
|
|
|
+ Click the button below to add rewards
|
|
|
+ </p>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- Address List -->
|
|
|
- <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
- <div class="p-6">
|
|
|
- <div class="flex justify-between items-center mb-6">
|
|
|
- <h2 class="text-2xl font-bold text-white">
|
|
|
- <i class="fas fa-list text-orange-300 mr-3"></i>
|
|
|
- Address List
|
|
|
- </h2>
|
|
|
- <div class="flex items-center space-x-3">
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- v-model="searchAddress"
|
|
|
- placeholder="Search addresses..."
|
|
|
- class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
|
|
|
- />
|
|
|
- <div class="badge badge-outline border-white/30 text-white">
|
|
|
- {{ filteredAddresses.length }}/{{ addressList.length }}
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="reward in waitAddRewardList"
|
|
|
+ :key="reward.id"
|
|
|
+ class="reward-item rounded-xl p-4 border border-white/20"
|
|
|
+ >
|
|
|
+ <div class="flex justify-between items-center">
|
|
|
+ <div class="flex items-center space-x-4">
|
|
|
+ <div class="w-12 h-12 icon-container backdrop-blur-sm">
|
|
|
+ <img
|
|
|
+ :src="getItemIcon(reward.id)"
|
|
|
+ :alt="getItemName(reward.id)"
|
|
|
+ class="w-8 h-8 item-icon"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <div class="font-semibold text-lg">
|
|
|
+ {{ getItemName(reward.id) }}
|
|
|
+ </div>
|
|
|
+ <div class="text-sm opacity-80">
|
|
|
+ {{ reward.count }} per address
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <input
|
|
|
+ type="number"
|
|
|
+ v-model="reward.count"
|
|
|
+ min="1"
|
|
|
+ class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
|
|
|
+ @change="updateRewardCount(reward.id, reward.count)"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
+ @click="removeWaitReward(reward.id)"
|
|
|
+ >
|
|
|
+ <i class="fas fa-times"></i>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div v-if="addressList.length === 0" class="text-center py-12">
|
|
|
- <div class="text-8xl mb-6 animate-bounce-slow">👤</div>
|
|
|
- <p class="text-purple-200 text-lg">No addresses yet</p>
|
|
|
- <p class="text-purple-300 text-sm mt-2">
|
|
|
- Please enter addresses above
|
|
|
- </p>
|
|
|
+ <div class="text-center mt-6">
|
|
|
+ <button
|
|
|
+ class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
|
|
|
+ @click="showAddRewardDialog = true"
|
|
|
+ >
|
|
|
+ <i class="fas fa-plus mr-2"></i>
|
|
|
+ Add Reward
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Step 3: Send Preview -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex items-center mb-6">
|
|
|
+ <div
|
|
|
+ class="w-12 h-12 bg-gradient-to-r from-green-500 to-blue-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
|
|
|
+ >
|
|
|
+ 3
|
|
|
+ </div>
|
|
|
+ <h2 class="text-3xl font-bold text-white">
|
|
|
+ <i class="fas fa-chart-line text-green-300 mr-3"></i>
|
|
|
+ Step 3: Review Summary
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Statistics -->
|
|
|
+ <div class="grid grid-cols-3 gap-6 mb-8">
|
|
|
+ <div class="stat-card rounded-xl p-6 text-center">
|
|
|
+ <div class="text-4xl font-bold text-white mb-2">
|
|
|
+ {{ addressList.length }}
|
|
|
+ </div>
|
|
|
+ <div class="text-lg text-purple-200">Addresses</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card rounded-xl p-6 text-center">
|
|
|
+ <div class="text-4xl font-bold text-white mb-2">
|
|
|
+ {{ totalRewardCount }}
|
|
|
+ </div>
|
|
|
+ <div class="text-lg text-purple-200">Rewards</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-card rounded-xl p-6 text-center">
|
|
|
+ <div class="text-4xl font-bold text-white mb-2">
|
|
|
+ {{ totalRewardCount * addressList.length }}
|
|
|
+ </div>
|
|
|
+ <div class="text-lg text-purple-200">Total</div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
|
|
|
+ <!-- Item Summary -->
|
|
|
+ <div v-if="waitAddRewardList.length > 0">
|
|
|
+ <h3 class="text-xl font-semibold text-white mb-4">
|
|
|
+ Item Summary
|
|
|
+ </h3>
|
|
|
+ <div
|
|
|
+ class="grid grid-cols-1 md:grid-cols-2 gap-4 max-h-60 overflow-y-auto scrollbar-thin"
|
|
|
+ >
|
|
|
<div
|
|
|
- v-for="(address, index) in filteredAddresses"
|
|
|
- :key="index"
|
|
|
- class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
|
|
|
+ v-for="reward in waitAddRewardList"
|
|
|
+ :key="reward.id"
|
|
|
+ class="flex justify-between items-center p-4 bg-white/10 rounded-lg backdrop-blur-sm"
|
|
|
>
|
|
|
- <div class="flex items-center space-x-4">
|
|
|
- <div
|
|
|
- class="w-8 h-8 bg-gradient-to-r from-purple-400 to-pink-400 rounded-full flex items-center justify-center text-xs font-bold text-white"
|
|
|
- >
|
|
|
- {{ index + 1 }}
|
|
|
- </div>
|
|
|
- <div class="font-mono text-sm text-white">
|
|
|
- {{ address }}
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <div class="w-10 h-10 icon-container">
|
|
|
+ <img
|
|
|
+ :src="getItemIcon(reward.id)"
|
|
|
+ :alt="getItemName(reward.id)"
|
|
|
+ class="w-8 h-8 item-icon"
|
|
|
+ @error="handleImageError"
|
|
|
+ />
|
|
|
</div>
|
|
|
+ <span class="font-medium text-white text-lg"
|
|
|
+ >{{ getItemName(reward.id) }}</span
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center space-x-2">
|
|
|
+ <span class="text-sm text-purple-200"
|
|
|
+ >{{ reward.count }} × {{ addressList.length }}</span
|
|
|
+ >
|
|
|
+ <span class="font-bold text-yellow-300 text-lg"
|
|
|
+ >= {{ reward.count * addressList.length }}</span
|
|
|
+ >
|
|
|
</div>
|
|
|
- <button
|
|
|
- class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
|
|
|
- @click="removeAddress(index)"
|
|
|
- >
|
|
|
- <i class="fas fa-times"></i>
|
|
|
- </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <!-- Bottom Send Button -->
|
|
|
- <div class="mt-12 text-center relative z-10">
|
|
|
- <button
|
|
|
- class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow neon-glow"
|
|
|
- @click="sendAllRewards"
|
|
|
- :disabled="addressList.length === 0 || waitAddRewardList.length === 0"
|
|
|
- >
|
|
|
- <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
|
|
|
- Launch Airdrop ({{ totalRewardCount * addressList.length }} rewards)
|
|
|
- </button>
|
|
|
+ <!-- Step 4: Launch Airdrop -->
|
|
|
+ <div class="glass-effect rounded-2xl shadow-2xl card-hover">
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="flex items-center mb-6">
|
|
|
+ <div
|
|
|
+ class="w-12 h-12 bg-gradient-to-r from-red-500 to-orange-600 rounded-full flex items-center justify-center text-white font-bold text-xl mr-4"
|
|
|
+ >
|
|
|
+ 4
|
|
|
+ </div>
|
|
|
+ <h2 class="text-3xl font-bold text-white">
|
|
|
+ <i class="fas fa-rocket text-red-300 mr-3"></i>
|
|
|
+ Step 4: Launch Airdrop
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="mb-8">
|
|
|
+ <p class="text-xl text-purple-200 mb-4">
|
|
|
+ Ready to send rewards to {{ addressList.length }} addresses
|
|
|
+ </p>
|
|
|
+ <p class="text-lg text-purple-300">
|
|
|
+ Total rewards to distribute: {{ totalRewardCount *
|
|
|
+ addressList.length }}
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow neon-glow"
|
|
|
+ @click="sendAllRewards"
|
|
|
+ :disabled="addressList.length === 0 || waitAddRewardList.length === 0"
|
|
|
+ >
|
|
|
+ <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
|
|
|
+ Launch Airdrop ({{ totalRewardCount * addressList.length }}
|
|
|
+ rewards)
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Add Reward Modal -->
|
|
@@ -612,11 +694,91 @@
|
|
|
<div class="modal-backdrop" @click="closeModal"></div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- Password Verification Modal -->
|
|
|
+ <div class="modal" :class="{ 'modal-open': showPasswordDialog }">
|
|
|
+ <div class="modal-box max-w-md glass-effect border border-white/20">
|
|
|
+ <h3 class="font-bold text-2xl mb-6 text-white text-center">
|
|
|
+ <i class="fas fa-lock text-red-300 mr-2"></i>
|
|
|
+ Security Verification
|
|
|
+ </h3>
|
|
|
+ <div class="space-y-6">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="text-6xl mb-4 animate-bounce-slow">🔐</div>
|
|
|
+ <p class="text-purple-200 text-lg mb-2">
|
|
|
+ Confirm Airdrop Operation
|
|
|
+ </p>
|
|
|
+ <p class="text-purple-300 text-sm">
|
|
|
+ This action will send {{ totalRewardCount * addressList.length
|
|
|
+ }} rewards to {{ addressList.length }} addresses
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-control">
|
|
|
+ <label class="label">
|
|
|
+ <span class="label-text font-semibold text-white text-lg"
|
|
|
+ >Enter Admin Password</span
|
|
|
+ >
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="password"
|
|
|
+ v-model="password"
|
|
|
+ class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
|
|
|
+ placeholder="Enter password"
|
|
|
+ @keyup.enter="verifyPassword"
|
|
|
+ />
|
|
|
+ <label class="label">
|
|
|
+ <span class="label-text-alt text-purple-200"
|
|
|
+ >Press Enter to confirm</span
|
|
|
+ >
|
|
|
+ </label>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-action">
|
|
|
+ <button
|
|
|
+ class="btn btn-primary btn-lg gradient-bg-3 border-0 text-white"
|
|
|
+ @click="verifyPassword"
|
|
|
+ :disabled="!password.trim() || isVerifying"
|
|
|
+ >
|
|
|
+ <i v-if="isVerifying" class="fas fa-spinner fa-spin mr-2"></i>
|
|
|
+ <i v-else class="fas fa-check mr-2"></i>
|
|
|
+ {{ isVerifying ? 'Verifying...' : 'Confirm Airdrop' }}
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="btn btn-ghost btn-lg text-white"
|
|
|
+ @click="closePasswordModal"
|
|
|
+ >
|
|
|
+ Cancel
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="modal-backdrop" @click="closePasswordModal"></div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- Toast Container -->
|
|
|
- <div class="toast toast-top toast-end">
|
|
|
- <div v-if="toast.show" :class="['alert', toast.type, 'glass-effect']">
|
|
|
- <div>
|
|
|
- <span class="text-white">{{ toast.message }}</span>
|
|
|
+ <div class="fixed top-4 right-4 z-50">
|
|
|
+ <div
|
|
|
+ v-if="toast.show"
|
|
|
+ :class="['alert', toast.type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']"
|
|
|
+ >
|
|
|
+ <div class="flex items-center justify-between w-full">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <i
|
|
|
+ v-if="toast.type === 'alert-success'"
|
|
|
+ class="fas fa-check-circle text-green-400 mr-3"
|
|
|
+ ></i>
|
|
|
+ <i
|
|
|
+ v-else-if="toast.type === 'alert-error'"
|
|
|
+ class="fas fa-exclamation-circle text-red-400 mr-3"
|
|
|
+ ></i>
|
|
|
+ <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
|
|
|
+ <span class="text-white font-medium">{{ toast.message }}</span>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ @click="toast.show = false"
|
|
|
+ class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
|
|
|
+ >
|
|
|
+ <i class="fas fa-times text-white"></i>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -628,15 +790,27 @@
|
|
|
|
|
|
const Toast = {
|
|
|
template: `
|
|
|
- <div class="toast toast-top toast-end">
|
|
|
- <div v-if="show" :class="['alert', type, 'glass-effect']">
|
|
|
- <div>
|
|
|
- <span class="text-white">{{ message }}</span>
|
|
|
+ <div class="fixed top-4 right-4 z-50">
|
|
|
+ <div v-if="show" :class="['alert', type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']">
|
|
|
+ <div class="flex items-center justify-between w-full">
|
|
|
+ <div class="flex items-center">
|
|
|
+ <i v-if="type === 'alert-success'" class="fas fa-check-circle text-green-400 mr-3"></i>
|
|
|
+ <i v-else-if="type === 'alert-error'" class="fas fa-exclamation-circle text-red-400 mr-3"></i>
|
|
|
+ <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
|
|
|
+ <span class="text-white font-medium">{{ message }}</span>
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ @click="$emit('close')"
|
|
|
+ class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
|
|
|
+ >
|
|
|
+ <i class="fas fa-times text-white"></i>
|
|
|
+ </button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`,
|
|
|
props: ["show", "type", "message"],
|
|
|
+ emits: ["close"],
|
|
|
};
|
|
|
|
|
|
createApp({
|
|
@@ -646,8 +820,11 @@
|
|
|
addressList: [],
|
|
|
waitAddRewardList: [],
|
|
|
showAddRewardDialog: false,
|
|
|
+ showPasswordDialog: false,
|
|
|
selectedReward: "",
|
|
|
rewardQuantity: "",
|
|
|
+ password: "",
|
|
|
+ isVerifying: false,
|
|
|
searchAddress: "",
|
|
|
staticGoods: [],
|
|
|
toast: {
|
|
@@ -863,32 +1040,81 @@
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // Build send data
|
|
|
- const sendData = {
|
|
|
- addresses: this.addressList,
|
|
|
- rewards: this.waitAddRewardList,
|
|
|
- totalAddresses: this.addressList.length,
|
|
|
- totalRewards: this.totalRewardCount,
|
|
|
- rewardTypes: this.waitAddRewardList.length,
|
|
|
- };
|
|
|
+ // Show password verification dialog
|
|
|
+ this.showPasswordDialog = true;
|
|
|
+ this.password = "";
|
|
|
+ },
|
|
|
|
|
|
- console.log("Send data:", sendData);
|
|
|
+ // Verify password and proceed with airdrop
|
|
|
+ async verifyPassword() {
|
|
|
+ if (!this.password.trim()) {
|
|
|
+ this.showToast("Please enter password", "alert-error");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- // Here you can add actual API call
|
|
|
- // Example: call send API
|
|
|
- this.showToast(
|
|
|
- `Preparing to send ${this.totalRewardCount} rewards to ${this.addressList.length} addresses`,
|
|
|
- "alert-info"
|
|
|
- );
|
|
|
+ this.isVerifying = true;
|
|
|
|
|
|
- // Simulate sending process
|
|
|
- setTimeout(() => {
|
|
|
+ try {
|
|
|
+ // Build send data
|
|
|
+ const sendData = {
|
|
|
+ addresses: this.addressList,
|
|
|
+ rewards: this.waitAddRewardList,
|
|
|
+ totalAddresses: this.addressList.length,
|
|
|
+ totalRewards: this.totalRewardCount,
|
|
|
+ rewardTypes: this.waitAddRewardList.length,
|
|
|
+ password: this.password,
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log("Sending data to server:", sendData);
|
|
|
+
|
|
|
+ this.showToast(
|
|
|
+ `Verifying password and preparing airdrop...`,
|
|
|
+ "alert-info"
|
|
|
+ );
|
|
|
+
|
|
|
+ // Call server API for password verification and airdrop
|
|
|
+ const response = await fetch(`${API_BASE_URL}/airdrop`, {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ body: JSON.stringify(sendData),
|
|
|
+ });
|
|
|
+
|
|
|
+ const result = await response.json();
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ this.closePasswordModal();
|
|
|
+ this.showToast(
|
|
|
+ "Airdrop completed successfully!",
|
|
|
+ "alert-success"
|
|
|
+ );
|
|
|
+ console.log("Airdrop result:", result);
|
|
|
+ } else {
|
|
|
+ this.showToast(
|
|
|
+ result.message ||
|
|
|
+ "Airdrop failed. Please check your password and try again.",
|
|
|
+ "alert-error"
|
|
|
+ );
|
|
|
+ this.password = "";
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("Airdrop error:", error);
|
|
|
this.showToast(
|
|
|
- "Airdrop completed successfully!",
|
|
|
- "alert-success"
|
|
|
+ "Network error. Please check your connection and try again.",
|
|
|
+ "alert-error"
|
|
|
);
|
|
|
- // You can clear lists or keep data after sending
|
|
|
- }, 2000);
|
|
|
+ this.password = "";
|
|
|
+ } finally {
|
|
|
+ this.isVerifying = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // Close password modal
|
|
|
+ closePasswordModal() {
|
|
|
+ this.showPasswordDialog = false;
|
|
|
+ this.password = "";
|
|
|
+ this.isVerifying = false;
|
|
|
},
|
|
|
|
|
|
closeModal() {
|
|
@@ -898,14 +1124,22 @@
|
|
|
},
|
|
|
|
|
|
showToast(message, type = "alert-info") {
|
|
|
- this.toast = {
|
|
|
- show: true,
|
|
|
- type: type,
|
|
|
- message: message,
|
|
|
- };
|
|
|
+ // Hide existing toast first
|
|
|
+ this.toast.show = false;
|
|
|
+
|
|
|
+ // Show new toast after a brief delay
|
|
|
setTimeout(() => {
|
|
|
- this.toast.show = false;
|
|
|
- }, 3000);
|
|
|
+ this.toast = {
|
|
|
+ show: true,
|
|
|
+ type: type,
|
|
|
+ message: message,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Auto hide after 4 seconds
|
|
|
+ setTimeout(() => {
|
|
|
+ this.toast.show = false;
|
|
|
+ }, 4000);
|
|
|
+ }, 100);
|
|
|
},
|
|
|
},
|
|
|
|