xxsanksdkfhsjdfbsfs3987cn102pom8923dfsdfhjdsf.html 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219
  1. <!DOCTYPE html>
  2. <html lang="en" data-theme="light">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>POG Airdrop</title>
  7. <script src="https://cdn.tailwindcss.com"></script>
  8. <link
  9. href="https://cdn.bootcdn.net/ajax/libs/daisyui/4.12.23/full.css"
  10. rel="stylesheet"
  11. type="text/css"
  12. />
  13. <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.5.13/vue.global.min.js"></script>
  14. <link
  15. href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
  16. rel="stylesheet"
  17. />
  18. <style>
  19. @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap");
  20. * {
  21. font-family: "Inter", sans-serif;
  22. }
  23. .gradient-bg {
  24. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  25. }
  26. .gradient-bg-2 {
  27. background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  28. }
  29. .gradient-bg-3 {
  30. background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  31. }
  32. .card-hover {
  33. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  34. }
  35. .card-hover:hover {
  36. transform: translateY(-4px);
  37. box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
  38. }
  39. .scrollbar-thin {
  40. scrollbar-width: thin;
  41. scrollbar-color: #cbd5e0 #f7fafc;
  42. }
  43. .scrollbar-thin::-webkit-scrollbar {
  44. width: 6px;
  45. }
  46. .scrollbar-thin::-webkit-scrollbar-track {
  47. background: #f7fafc;
  48. }
  49. .scrollbar-thin::-webkit-scrollbar-thumb {
  50. background: #cbd5e0;
  51. border-radius: 3px;
  52. }
  53. .animate-pulse-slow {
  54. animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  55. }
  56. .animate-bounce-slow {
  57. animation: bounce 2s infinite;
  58. }
  59. .glass-effect {
  60. background: rgba(255, 255, 255, 0.25);
  61. backdrop-filter: blur(10px);
  62. border: 1px solid rgba(255, 255, 255, 0.18);
  63. }
  64. .neon-glow {
  65. box-shadow: 0 0 20px rgba(102, 126, 234, 0.5);
  66. }
  67. .floating {
  68. animation: floating 3s ease-in-out infinite;
  69. }
  70. @keyframes floating {
  71. 0% {
  72. transform: translateY(0px);
  73. }
  74. 50% {
  75. transform: translateY(-10px);
  76. }
  77. 100% {
  78. transform: translateY(0px);
  79. }
  80. }
  81. .stat-card {
  82. background: linear-gradient(
  83. 135deg,
  84. rgba(255, 255, 255, 0.1) 0%,
  85. rgba(255, 255, 255, 0.05) 100%
  86. );
  87. backdrop-filter: blur(10px);
  88. border: 1px solid rgba(255, 255, 255, 0.2);
  89. }
  90. .reward-item {
  91. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  92. color: white;
  93. transition: all 0.3s ease;
  94. }
  95. .reward-item:hover {
  96. transform: scale(1.05);
  97. box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
  98. }
  99. .address-item {
  100. transition: all 0.3s ease;
  101. }
  102. .address-item:hover {
  103. background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  104. color: white;
  105. transform: translateX(5px);
  106. }
  107. .btn-glow {
  108. position: relative;
  109. overflow: hidden;
  110. }
  111. .btn-glow::before {
  112. content: "";
  113. position: absolute;
  114. top: 0;
  115. left: -100%;
  116. width: 100%;
  117. height: 100%;
  118. background: linear-gradient(
  119. 90deg,
  120. transparent,
  121. rgba(255, 255, 255, 0.4),
  122. transparent
  123. );
  124. transition: left 0.5s;
  125. }
  126. .btn-glow:hover::before {
  127. left: 100%;
  128. }
  129. .item-icon {
  130. width: 100%;
  131. height: 100%;
  132. object-fit: contain;
  133. transition: transform 0.3s ease;
  134. }
  135. .item-icon:hover {
  136. transform: scale(1.1);
  137. }
  138. .icon-container {
  139. display: flex;
  140. align-items: center;
  141. justify-content: center;
  142. background: rgba(255, 255, 255, 0.1);
  143. border: 1px solid rgba(255, 255, 255, 0.2);
  144. border-radius: 50%;
  145. overflow: hidden;
  146. transition: all 0.3s ease;
  147. }
  148. .icon-container:hover {
  149. background: rgba(255, 255, 255, 0.2);
  150. border-color: rgba(255, 255, 255, 0.4);
  151. }
  152. /* Toast Animation */
  153. .toast-enter-active,
  154. .toast-leave-active {
  155. transition: all 0.3s ease;
  156. }
  157. .toast-enter-from {
  158. opacity: 0;
  159. transform: translateX(100%);
  160. }
  161. .toast-leave-to {
  162. opacity: 0;
  163. transform: translateX(100%);
  164. }
  165. .toast-enter-to,
  166. .toast-leave-from {
  167. opacity: 1;
  168. transform: translateX(0);
  169. }
  170. .animate-slide-in {
  171. animation: slideIn 0.3s ease-out;
  172. }
  173. @keyframes slideIn {
  174. from {
  175. opacity: 0;
  176. transform: translateX(100%);
  177. }
  178. to {
  179. opacity: 1;
  180. transform: translateX(0);
  181. }
  182. }
  183. </style>
  184. </head>
  185. <body
  186. class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900"
  187. >
  188. <div id="app" class="container mx-auto p-4 md:p-8">
  189. <!-- Animated Background -->
  190. <div class="fixed inset-0 overflow-hidden pointer-events-none">
  191. <div
  192. class="absolute -top-40 -right-40 w-80 h-80 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
  193. ></div>
  194. <div
  195. class="absolute -bottom-40 -left-40 w-80 h-80 bg-pink-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
  196. style="animation-delay: 1s"
  197. ></div>
  198. <div
  199. class="absolute top-40 left-40 w-80 h-80 bg-blue-500 rounded-full mix-blend-multiply filter blur-xl opacity-20 animate-bounce-slow"
  200. style="animation-delay: 2s"
  201. ></div>
  202. </div>
  203. <!-- Header -->
  204. <div class="text-center mb-12 relative z-10">
  205. <div class="floating mb-4">
  206. <i class="fas fa-rocket text-6xl text-white mb-4"></i>
  207. </div>
  208. <h1 class="text-5xl font-bold text-white mb-4">POG Airdrop</h1>
  209. <p class="text-xl text-purple-200">Mass Reward Distribution System</p>
  210. <div class="flex justify-center mt-6 space-x-4">
  211. <div class="stat-card rounded-lg p-4 text-white">
  212. <div class="text-2xl font-bold">{{ addressList.length }}</div>
  213. <div class="text-sm opacity-80">Addresses</div>
  214. </div>
  215. <div class="stat-card rounded-lg p-4 text-white">
  216. <div class="text-2xl font-bold">{{ totalRewardCount }}</div>
  217. <div class="text-sm opacity-80">Rewards</div>
  218. </div>
  219. <div class="stat-card rounded-lg p-4 text-white">
  220. <div class="text-2xl font-bold">
  221. {{ totalRewardCount * addressList.length }}
  222. </div>
  223. <div class="text-sm opacity-80">Total</div>
  224. </div>
  225. </div>
  226. </div>
  227. <div class="space-y-8 relative z-10">
  228. <!-- Step 1: Address Management -->
  229. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  230. <div class="p-6">
  231. <div class="flex items-center mb-6">
  232. <div
  233. 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"
  234. >
  235. 1
  236. </div>
  237. <h2 class="text-3xl font-bold text-white">
  238. <i class="fas fa-users text-blue-300 mr-3"></i>
  239. Step 1: Add Wallet Addresses
  240. </h2>
  241. </div>
  242. <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
  243. <!-- Address Input -->
  244. <div>
  245. <div class="form-control">
  246. <label class="label">
  247. <span class="label-text font-semibold text-white text-lg"
  248. >Wallet Address List</span
  249. >
  250. <span class="label-text-alt text-purple-300"
  251. >{{ addressList.length }} addresses</span
  252. >
  253. </label>
  254. <textarea
  255. v-model="waitInputAddresses"
  256. rows="10"
  257. placeholder="Enter wallet addresses, one per line&#10;Example:&#10;0x1234567890abcdef...&#10;0xabcdef1234567890..."
  258. class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
  259. @input="parseAddresses"
  260. ></textarea>
  261. <label class="label">
  262. <span class="label-text-alt text-purple-200"
  263. >Support batch paste, one address per line</span
  264. >
  265. </label>
  266. </div>
  267. <!-- Quick Actions -->
  268. <div class="flex flex-wrap gap-2 mt-4">
  269. <button
  270. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  271. @click="clearAddresses"
  272. >
  273. <i class="fas fa-trash mr-1"></i> Clear
  274. </button>
  275. <button
  276. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  277. @click="exportAddresses"
  278. >
  279. <i class="fas fa-download mr-1"></i> Export
  280. </button>
  281. <button
  282. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  283. @click="importAddresses"
  284. >
  285. <i class="fas fa-upload mr-1"></i> Import
  286. </button>
  287. </div>
  288. </div>
  289. <!-- Address List -->
  290. <div>
  291. <div class="flex justify-between items-center mb-4">
  292. <h3 class="text-xl font-semibold text-white">
  293. <i class="fas fa-list text-orange-300 mr-2"></i>
  294. Address List
  295. </h3>
  296. <div class="flex items-center space-x-3">
  297. <input
  298. type="text"
  299. v-model="searchAddress"
  300. placeholder="Search addresses..."
  301. class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
  302. />
  303. <div class="badge badge-outline border-white/30 text-white">
  304. {{ filteredAddresses.length }}/{{ addressList.length }}
  305. </div>
  306. </div>
  307. </div>
  308. <div v-if="addressList.length === 0" class="text-center py-8">
  309. <div class="text-6xl mb-4 animate-bounce-slow">👤</div>
  310. <p class="text-purple-200 text-lg">No addresses yet</p>
  311. <p class="text-purple-300 text-sm mt-2">
  312. Please enter addresses on the left
  313. </p>
  314. </div>
  315. <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
  316. <div
  317. v-for="(address, index) in filteredAddresses"
  318. :key="index"
  319. class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
  320. >
  321. <div class="flex items-center space-x-4">
  322. <div
  323. 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"
  324. >
  325. {{ index + 1 }}
  326. </div>
  327. <div class="font-mono text-sm text-white">
  328. {{ address }}
  329. </div>
  330. </div>
  331. <button
  332. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  333. @click="removeAddress(index)"
  334. >
  335. <i class="fas fa-times"></i>
  336. </button>
  337. </div>
  338. </div>
  339. </div>
  340. </div>
  341. </div>
  342. </div>
  343. <!-- Step 2: Reward Management -->
  344. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  345. <div class="p-6">
  346. <div class="flex items-center mb-6">
  347. <div
  348. 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"
  349. >
  350. 2
  351. </div>
  352. <h2 class="text-3xl font-bold text-white">
  353. <i class="fas fa-gift text-purple-300 mr-3"></i>
  354. Step 2: Select Rewards
  355. </h2>
  356. <div class="badge badge-primary badge-lg animate-pulse-slow ml-4">
  357. {{ waitAddRewardList.length }} Types
  358. </div>
  359. </div>
  360. <div
  361. v-if="waitAddRewardList.length === 0"
  362. class="text-center py-12"
  363. >
  364. <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
  365. <p class="text-purple-200 text-lg">No rewards added yet</p>
  366. <p class="text-purple-300 text-sm mt-2">
  367. Click the button below to add rewards
  368. </p>
  369. </div>
  370. <div
  371. v-else
  372. class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
  373. >
  374. <div
  375. v-for="reward in waitAddRewardList"
  376. :key="reward.id"
  377. class="reward-item rounded-xl p-4 border border-white/20"
  378. >
  379. <div class="flex justify-between items-center">
  380. <div class="flex items-center space-x-4">
  381. <div class="w-12 h-12 icon-container backdrop-blur-sm">
  382. <img
  383. :src="getItemIcon(reward.id)"
  384. :alt="getItemName(reward.id)"
  385. class="w-8 h-8 item-icon"
  386. @error="handleImageError"
  387. />
  388. </div>
  389. <div>
  390. <div class="font-semibold text-lg">
  391. {{ getItemName(reward.id) }}
  392. </div>
  393. <div class="text-sm opacity-80">
  394. {{ reward.count }} per address
  395. </div>
  396. </div>
  397. </div>
  398. <div class="flex items-center space-x-3">
  399. <input
  400. type="number"
  401. v-model="reward.count"
  402. min="1"
  403. class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
  404. @change="updateRewardCount(reward.id, reward.count)"
  405. />
  406. <button
  407. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  408. @click="removeWaitReward(reward.id)"
  409. >
  410. <i class="fas fa-times"></i>
  411. </button>
  412. </div>
  413. </div>
  414. </div>
  415. </div>
  416. <div class="text-center mt-6">
  417. <button
  418. class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
  419. @click="showAddRewardDialog = true"
  420. >
  421. <i class="fas fa-plus mr-2"></i>
  422. Add Reward
  423. </button>
  424. </div>
  425. </div>
  426. </div>
  427. <!-- Step 3: Send Preview -->
  428. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  429. <div class="p-6">
  430. <div class="flex items-center mb-6">
  431. <div
  432. 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"
  433. >
  434. 3
  435. </div>
  436. <h2 class="text-3xl font-bold text-white">
  437. <i class="fas fa-chart-line text-green-300 mr-3"></i>
  438. Step 3: Review Summary
  439. </h2>
  440. </div>
  441. <!-- Statistics -->
  442. <div class="grid grid-cols-3 gap-6 mb-8">
  443. <div class="stat-card rounded-xl p-6 text-center">
  444. <div class="text-4xl font-bold text-white mb-2">
  445. {{ addressList.length }}
  446. </div>
  447. <div class="text-lg text-purple-200">Addresses</div>
  448. </div>
  449. <div class="stat-card rounded-xl p-6 text-center">
  450. <div class="text-4xl font-bold text-white mb-2">
  451. {{ totalRewardCount }}
  452. </div>
  453. <div class="text-lg text-purple-200">Rewards</div>
  454. </div>
  455. <div class="stat-card rounded-xl p-6 text-center">
  456. <div class="text-4xl font-bold text-white mb-2">
  457. {{ totalRewardCount * addressList.length }}
  458. </div>
  459. <div class="text-lg text-purple-200">Total</div>
  460. </div>
  461. </div>
  462. <!-- Item Summary -->
  463. <div v-if="waitAddRewardList.length > 0">
  464. <h3 class="text-xl font-semibold text-white mb-4">
  465. Item Summary
  466. </h3>
  467. <div
  468. class="grid grid-cols-1 md:grid-cols-2 gap-4 max-h-60 overflow-y-auto scrollbar-thin"
  469. >
  470. <div
  471. v-for="reward in waitAddRewardList"
  472. :key="reward.id"
  473. class="flex justify-between items-center p-4 bg-white/10 rounded-lg backdrop-blur-sm"
  474. >
  475. <div class="flex items-center space-x-3">
  476. <div class="w-10 h-10 icon-container">
  477. <img
  478. :src="getItemIcon(reward.id)"
  479. :alt="getItemName(reward.id)"
  480. class="w-8 h-8 item-icon"
  481. @error="handleImageError"
  482. />
  483. </div>
  484. <span class="font-medium text-white text-lg"
  485. >{{ getItemName(reward.id) }}</span
  486. >
  487. </div>
  488. <div class="flex items-center space-x-2">
  489. <span class="text-sm text-purple-200"
  490. >{{ reward.count }} × {{ addressList.length }}</span
  491. >
  492. <span class="font-bold text-yellow-300 text-lg"
  493. >= {{ reward.count * addressList.length }}</span
  494. >
  495. </div>
  496. </div>
  497. </div>
  498. </div>
  499. </div>
  500. </div>
  501. <!-- Step 4: Launch Airdrop -->
  502. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  503. <div class="p-6">
  504. <div class="flex items-center mb-6">
  505. <div
  506. 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"
  507. >
  508. 4
  509. </div>
  510. <h2 class="text-3xl font-bold text-white">
  511. <i class="fas fa-rocket text-red-300 mr-3"></i>
  512. Step 4: Launch Airdrop
  513. </h2>
  514. </div>
  515. <div class="text-center">
  516. <div class="mb-8">
  517. <p class="text-xl text-purple-200 mb-4">
  518. Ready to send rewards to {{ addressList.length }} addresses
  519. </p>
  520. <p class="text-lg text-purple-300">
  521. Total rewards to distribute: {{ totalRewardCount *
  522. addressList.length }}
  523. </p>
  524. </div>
  525. <button
  526. class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow"
  527. @click="sendAllRewards"
  528. :disabled="addressList.length === 0 || waitAddRewardList.length === 0"
  529. >
  530. <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
  531. Launch Airdrop ({{ totalRewardCount * addressList.length }}
  532. rewards)
  533. </button>
  534. </div>
  535. </div>
  536. </div>
  537. </div>
  538. <!-- Add Reward Modal -->
  539. <div class="modal" :class="{ 'modal-open': showAddRewardDialog }">
  540. <div
  541. class="modal-box w-11/12 max-w-4xl glass-effect border border-white/20"
  542. >
  543. <h3 class="font-bold text-3xl mb-8 text-white text-center">
  544. <i class="fas fa-gift text-purple-300 mr-2"></i>
  545. Add Reward
  546. </h3>
  547. <div class="space-y-6 max-h-[80vh] overflow-y-auto">
  548. <div class="form-control">
  549. <label class="label">
  550. <span class="label-text font-semibold text-white text-lg"
  551. >Select Item</span
  552. >
  553. </label>
  554. <div
  555. class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 max-h-96 overflow-y-auto scrollbar-thin"
  556. >
  557. <div
  558. v-for="item in staticGoods"
  559. :key="item.id"
  560. @click="selectedReward = item.id"
  561. :class="[
  562. 'p-4 rounded-xl border-2 cursor-pointer transition-all duration-200 hover:scale-105',
  563. selectedReward == item.id
  564. ? 'border-purple-400 bg-purple-500/20 shadow-lg shadow-purple-500/30'
  565. : 'border-white/20 bg-white/5 hover:bg-white/10 hover:border-white/40'
  566. ]"
  567. >
  568. <div class="flex flex-col items-center space-y-3 text-center">
  569. <div class="w-16 h-16 icon-container">
  570. <img
  571. :src="getItemIcon(item.id)"
  572. :alt="item.name"
  573. class="w-12 h-12 item-icon"
  574. @error="handleImageError"
  575. />
  576. </div>
  577. <div>
  578. <div class="font-semibold text-white text-base">
  579. {{ item.name }}
  580. </div>
  581. <div class="text-sm text-purple-200 mt-1">
  582. Stock: {{ item.count }}
  583. </div>
  584. </div>
  585. </div>
  586. </div>
  587. </div>
  588. </div>
  589. <div class="form-control">
  590. <label class="label">
  591. <span class="label-text font-semibold text-white text-lg"
  592. >Quantity per Address</span
  593. >
  594. </label>
  595. <input
  596. type="number"
  597. v-model="rewardQuantity"
  598. min="1"
  599. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  600. placeholder="Enter quantity"
  601. />
  602. </div>
  603. </div>
  604. <div class="modal-action">
  605. <button
  606. class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white"
  607. @click="addRewardConfirm"
  608. :disabled="!selectedReward || !rewardQuantity || rewardQuantity <= 0"
  609. >
  610. <i class="fas fa-check mr-2"></i>
  611. Add Reward
  612. </button>
  613. <button class="btn btn-ghost btn-lg text-white" @click="closeModal">
  614. Cancel
  615. </button>
  616. </div>
  617. </div>
  618. <div class="modal-backdrop" @click="closeModal"></div>
  619. </div>
  620. <!-- Password Verification Modal -->
  621. <div class="modal" :class="{ 'modal-open': showPasswordDialog }">
  622. <div class="modal-box max-w-md glass-effect border border-white/20">
  623. <h3 class="font-bold text-2xl mb-6 text-white text-center">
  624. <i class="fas fa-shield-alt text-red-300 mr-2"></i>
  625. Final Confirmation
  626. </h3>
  627. <div class="space-y-6">
  628. <div class="text-center">
  629. <div class="text-6xl mb-4 animate-bounce-slow">🔐</div>
  630. <p class="text-purple-200 text-lg mb-2">
  631. Confirm Airdrop Operation
  632. </p>
  633. <p class="text-purple-300 text-sm">
  634. This action will send {{ totalRewardCount * addressList.length
  635. }} rewards to {{ addressList.length }} addresses
  636. </p>
  637. </div>
  638. <div class="grid grid-cols-1 gap-6">
  639. <div class="form-control">
  640. <label class="label">
  641. <span class="label-text font-semibold text-white text-lg"
  642. ><i class="fas fa-tag text-blue-300 mr-2"></i>Activity
  643. Name</span
  644. >
  645. </label>
  646. <input
  647. type="text"
  648. v-model="reason"
  649. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  650. placeholder="Enter activity name (e.g., Spring Festival Event)"
  651. @keyup.enter="verifyPassword"
  652. />
  653. <label class="label">
  654. <span class="label-text-alt text-purple-200"
  655. >This will be used to identify the airdrop activity</span
  656. >
  657. </label>
  658. </div>
  659. <div class="form-control">
  660. <label class="label">
  661. <span class="label-text font-semibold text-white text-lg"
  662. ><i class="fas fa-lock text-red-300 mr-2"></i>Admin
  663. Password</span
  664. >
  665. </label>
  666. <input
  667. type="password"
  668. v-model="password"
  669. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50 text-lg text-center"
  670. placeholder="Enter password"
  671. @keyup.enter="verifyPassword"
  672. />
  673. <label class="label">
  674. <span class="label-text-alt text-purple-200"
  675. >Press Enter to confirm</span
  676. >
  677. </label>
  678. </div>
  679. </div>
  680. </div>
  681. <div class="modal-action">
  682. <button
  683. class="btn btn-primary btn-lg gradient-bg-3 border-0 text-white"
  684. @click="verifyPassword"
  685. :disabled="!password.trim() || !reason.trim() || isVerifying"
  686. >
  687. <i v-if="isVerifying" class="fas fa-spinner fa-spin mr-2"></i>
  688. <i v-else class="fas fa-rocket mr-2"></i>
  689. {{ isVerifying ? 'Verifying...' : 'Launch Airdrop' }}
  690. </button>
  691. <button
  692. class="btn btn-ghost btn-lg text-white"
  693. @click="closePasswordModal"
  694. >
  695. Cancel
  696. </button>
  697. </div>
  698. </div>
  699. <div class="modal-backdrop" @click="closePasswordModal"></div>
  700. </div>
  701. <!-- Toast Container -->
  702. <div class="fixed top-4 right-4 z-50">
  703. <div
  704. v-if="toast.show"
  705. :class="['alert', toast.type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']"
  706. >
  707. <div class="flex items-center justify-between w-full">
  708. <div class="flex items-center">
  709. <i
  710. v-if="toast.type === 'alert-success'"
  711. class="fas fa-check-circle text-green-400 mr-3"
  712. ></i>
  713. <i
  714. v-else-if="toast.type === 'alert-error'"
  715. class="fas fa-exclamation-circle text-red-400 mr-3"
  716. ></i>
  717. <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
  718. <span class="text-white font-medium">{{ toast.message }}</span>
  719. </div>
  720. <button
  721. @click="toast.show = false"
  722. class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
  723. >
  724. <i class="fas fa-times text-white"></i>
  725. </button>
  726. </div>
  727. </div>
  728. </div>
  729. </div>
  730. <script>
  731. const { createApp } = Vue;
  732. const API_BASE_URL = "https://zombies.telgather.com/platform";
  733. const Toast = {
  734. template: `
  735. <div class="fixed top-4 right-4 z-50">
  736. <div v-if="show" :class="['alert', type, 'glass-effect', 'shadow-2xl', 'min-w-80', 'max-w-md', 'animate-slide-in']">
  737. <div class="flex items-center justify-between w-full">
  738. <div class="flex items-center">
  739. <i v-if="type === 'alert-success'" class="fas fa-check-circle text-green-400 mr-3"></i>
  740. <i v-else-if="type === 'alert-error'" class="fas fa-exclamation-circle text-red-400 mr-3"></i>
  741. <i v-else class="fas fa-info-circle text-blue-400 mr-3"></i>
  742. <span class="text-white font-medium">{{ message }}</span>
  743. </div>
  744. <button
  745. @click="$emit('close')"
  746. class="btn btn-circle btn-sm bg-white/20 border-0 hover:bg-white/30 ml-3"
  747. >
  748. <i class="fas fa-times text-white"></i>
  749. </button>
  750. </div>
  751. </div>
  752. </div>
  753. `,
  754. props: ["show", "type", "message"],
  755. emits: ["close"],
  756. };
  757. createApp({
  758. data() {
  759. return {
  760. reason: "",
  761. waitInputAddresses: "",
  762. addressList: [],
  763. waitAddRewardList: [],
  764. showAddRewardDialog: false,
  765. showPasswordDialog: false,
  766. selectedReward: "",
  767. rewardQuantity: "",
  768. password: "",
  769. isVerifying: false,
  770. searchAddress: "",
  771. staticGoods: [],
  772. toast: {
  773. show: false,
  774. type: "alert-info",
  775. message: "",
  776. },
  777. };
  778. },
  779. computed: {
  780. totalRewardCount() {
  781. return this.waitAddRewardList.reduce(
  782. (total, reward) => total + reward.count,
  783. 0
  784. );
  785. },
  786. filteredAddresses() {
  787. if (!this.searchAddress) return this.addressList;
  788. return this.addressList.filter((addr) =>
  789. addr.toLowerCase().includes(this.searchAddress.toLowerCase())
  790. );
  791. },
  792. },
  793. components: {
  794. Toast,
  795. },
  796. methods: {
  797. // Parse addresses
  798. parseAddresses() {
  799. if (this.waitInputAddresses.trim()) {
  800. const addresses = this.waitInputAddresses
  801. .split("\n")
  802. .map((addr) => addr.trim())
  803. .filter((addr) => addr && addr.length > 0);
  804. this.addressList = addresses;
  805. } else {
  806. this.addressList = [];
  807. }
  808. },
  809. // Remove address
  810. removeAddress(index) {
  811. this.addressList.splice(index, 1);
  812. this.updateAddressInput();
  813. this.showToast(`Address removed`, "alert-info");
  814. },
  815. // Update address input
  816. updateAddressInput() {
  817. this.waitInputAddresses = this.addressList.join("\n");
  818. },
  819. // Clear addresses
  820. clearAddresses() {
  821. this.addressList = [];
  822. this.waitInputAddresses = "";
  823. this.showToast("All addresses cleared", "alert-info");
  824. },
  825. // Export addresses
  826. exportAddresses() {
  827. if (this.addressList.length === 0) {
  828. this.showToast("No addresses to export", "alert-error");
  829. return;
  830. }
  831. const text = this.addressList.join("\n");
  832. const blob = new Blob([text], { type: "text/plain" });
  833. const url = URL.createObjectURL(blob);
  834. const a = document.createElement("a");
  835. a.href = url;
  836. a.download = `addresses_${
  837. new Date().toISOString().split("T")[0]
  838. }.txt`;
  839. a.click();
  840. URL.revokeObjectURL(url);
  841. this.showToast(
  842. `Exported ${this.addressList.length} addresses`,
  843. "alert-success"
  844. );
  845. },
  846. // Import addresses
  847. importAddresses() {
  848. const input = document.createElement("input");
  849. input.type = "file";
  850. input.accept = ".txt,.csv";
  851. input.onchange = (e) => {
  852. const file = e.target.files[0];
  853. if (file) {
  854. const reader = new FileReader();
  855. reader.onload = (e) => {
  856. const content = e.target.result;
  857. this.waitInputAddresses = content;
  858. this.parseAddresses();
  859. this.showToast(
  860. `Imported ${this.addressList.length} addresses`,
  861. "alert-success"
  862. );
  863. };
  864. reader.readAsText(file);
  865. }
  866. };
  867. input.click();
  868. },
  869. // Add reward confirmation
  870. addRewardConfirm() {
  871. if (
  872. this.selectedReward &&
  873. this.rewardQuantity &&
  874. this.rewardQuantity > 0
  875. ) {
  876. const itemId = parseInt(this.selectedReward);
  877. const count = parseInt(this.rewardQuantity);
  878. // Check if item already exists
  879. const existingIndex = this.waitAddRewardList.findIndex(
  880. (item) => item.id === itemId
  881. );
  882. if (existingIndex > -1) {
  883. // If exists, increase quantity
  884. this.waitAddRewardList[existingIndex].count += count;
  885. this.showToast(
  886. `Added ${count} more ${this.getItemName(itemId)}`,
  887. "alert-success"
  888. );
  889. } else {
  890. // If not exists, add new item
  891. this.waitAddRewardList.push({
  892. id: itemId,
  893. count: count,
  894. });
  895. this.showToast(
  896. `Added ${count} ${this.getItemName(itemId)}`,
  897. "alert-success"
  898. );
  899. }
  900. this.closeModal();
  901. }
  902. },
  903. // Update reward count
  904. updateRewardCount(id, count) {
  905. const reward = this.waitAddRewardList.find(
  906. (item) => item.id === id
  907. );
  908. if (reward && count > 0) {
  909. reward.count = parseInt(count);
  910. this.showToast(
  911. `Updated ${this.getItemName(id)} quantity to ${count}`,
  912. "alert-info"
  913. );
  914. }
  915. },
  916. // Remove reward
  917. removeWaitReward(id) {
  918. const index = this.waitAddRewardList.findIndex(
  919. (item) => item.id === id
  920. );
  921. if (index > -1) {
  922. const removedReward = this.waitAddRewardList.splice(index, 1)[0];
  923. this.showToast(
  924. `Removed ${removedReward.count} ${this.getItemName(id)}`,
  925. "alert-info"
  926. );
  927. }
  928. },
  929. // Get item name
  930. getItemName(id) {
  931. const item = this.staticGoods.find((item) => item.id == id);
  932. return item ? item.name : `Unknown Item (${id})`;
  933. },
  934. // Get item icon
  935. getItemIcon(id) {
  936. const item = this.staticGoods.find((item) => item.id == id);
  937. if (item && item.icon) {
  938. const iconPath = `imgs/${item.icon}.png`;
  939. console.log(`Loading icon for item ${id}: ${iconPath}`);
  940. return iconPath;
  941. }
  942. console.log(`No icon found for item ${id}, using default`);
  943. return "imgs/default_icon.png";
  944. },
  945. // Handle image error
  946. handleImageError(event) {
  947. console.log("Image load error:", event.target.src);
  948. // Replace with fallback icon
  949. const parent = event.target.parentElement;
  950. if (parent) {
  951. parent.innerHTML =
  952. '<i class="fas fa-question text-white/50 text-xl"></i>';
  953. }
  954. },
  955. // Send all rewards
  956. sendAllRewards() {
  957. if (this.addressList.length === 0) {
  958. this.showToast(
  959. "Please add wallet addresses first",
  960. "alert-error"
  961. );
  962. return;
  963. }
  964. if (this.waitAddRewardList.length === 0) {
  965. this.showToast("Please add rewards first", "alert-error");
  966. return;
  967. }
  968. // Show password verification dialog
  969. this.showPasswordDialog = true;
  970. this.password = "";
  971. this.reason = "";
  972. },
  973. // Verify password and proceed with airdrop
  974. async verifyPassword() {
  975. if (!this.password.trim()) {
  976. this.showToast("Please enter password", "alert-error");
  977. return;
  978. }
  979. if (!this.reason.trim()) {
  980. this.showToast("Please enter reason", "alert-error");
  981. return;
  982. }
  983. this.isVerifying = true;
  984. let goodList = [];
  985. for (const item of this.waitAddRewardList) {
  986. goodList.push({
  987. id: item.id,
  988. count: item.count,
  989. });
  990. }
  991. try {
  992. // Build send data
  993. const sendData = {
  994. reason: this.reason,
  995. walletList: this.addressList,
  996. goodList: goodList,
  997. password: this.password,
  998. };
  999. console.log("Sending data to server:", sendData);
  1000. this.showToast(
  1001. `Verifying password and preparing airdrop...`,
  1002. "alert-info"
  1003. );
  1004. // Call server API for password verification and airdrop
  1005. const response = await fetch(
  1006. `${API_BASE_URL}/pog-service/callback/add/good`,
  1007. {
  1008. method: "POST",
  1009. headers: {
  1010. "Content-Type": "application/json",
  1011. },
  1012. body: JSON.stringify(sendData),
  1013. }
  1014. );
  1015. const result = await response.json();
  1016. if (response.ok) {
  1017. this.closePasswordModal();
  1018. this.showToast(
  1019. "Airdrop completed successfully!",
  1020. "alert-success"
  1021. );
  1022. console.log("Airdrop result:", result);
  1023. } else {
  1024. this.showToast(
  1025. result.message ||
  1026. "Airdrop failed. Please check your password and try again.",
  1027. "alert-error"
  1028. );
  1029. this.password = "";
  1030. }
  1031. } catch (error) {
  1032. console.error("Airdrop error:", error);
  1033. this.showToast(
  1034. "Network error. Please check your connection and try again.",
  1035. "alert-error"
  1036. );
  1037. this.password = "";
  1038. } finally {
  1039. this.isVerifying = false;
  1040. }
  1041. },
  1042. // Close password modal
  1043. closePasswordModal() {
  1044. this.showPasswordDialog = false;
  1045. this.password = "";
  1046. this.reason = "";
  1047. this.isVerifying = false;
  1048. },
  1049. closeModal() {
  1050. this.showAddRewardDialog = false;
  1051. this.selectedReward = "";
  1052. this.rewardQuantity = "";
  1053. },
  1054. showToast(message, type = "alert-info") {
  1055. // Hide existing toast first
  1056. this.toast.show = false;
  1057. // Show new toast after a brief delay
  1058. setTimeout(() => {
  1059. this.toast = {
  1060. show: true,
  1061. type: type,
  1062. message: message,
  1063. };
  1064. // Auto hide after 4 seconds
  1065. setTimeout(() => {
  1066. this.toast.show = false;
  1067. }, 4000);
  1068. }, 100);
  1069. },
  1070. },
  1071. mounted() {
  1072. const m = {
  1073. 1: {
  1074. name: "POG",
  1075. count: 100,
  1076. icon: "icon_pog",
  1077. },
  1078. 2: {
  1079. name: "Diamond",
  1080. count: 100,
  1081. icon: "icon_gem",
  1082. },
  1083. 3: {
  1084. name: "GAME_SHARD",
  1085. count: 100,
  1086. icon: "icon_puzzle",
  1087. },
  1088. 5: {
  1089. name: "CRIT_CARD",
  1090. count: 100,
  1091. icon: "icon_crit",
  1092. },
  1093. 6: {
  1094. name: "FREE_ITEM_BOX",
  1095. count: 100,
  1096. icon: "icon_item_box",
  1097. },
  1098. 7: {
  1099. name: "ITEM_BOX",
  1100. count: 100,
  1101. icon: "icon_item_box",
  1102. },
  1103. 8: {
  1104. name: "FREE_POG_BOX",
  1105. count: 100,
  1106. icon: "icon_pog_box",
  1107. },
  1108. 9: {
  1109. name: "POG_BOX",
  1110. count: 100,
  1111. icon: "icon_pog_box",
  1112. },
  1113. };
  1114. this.staticGoods = [];
  1115. for (const key in m) {
  1116. this.staticGoods.push({
  1117. id: key,
  1118. name: m[key].name,
  1119. count: m[key].count,
  1120. icon: m[key].icon,
  1121. });
  1122. }
  1123. console.log(this.staticGoods);
  1124. },
  1125. }).mount("#app");
  1126. </script>
  1127. </body>
  1128. </html>