xxsanksdkfhsjdfbsfs3987cn102pom8923dfsdfhjdsf.html 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  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. .shimmer {
  82. background: linear-gradient(
  83. 90deg,
  84. transparent,
  85. rgba(255, 255, 255, 0.4),
  86. transparent
  87. );
  88. background-size: 200% 100%;
  89. animation: shimmer 2s infinite;
  90. }
  91. @keyframes shimmer {
  92. 0% {
  93. background-position: -200% 0;
  94. }
  95. 100% {
  96. background-position: 200% 0;
  97. }
  98. }
  99. .stat-card {
  100. background: linear-gradient(
  101. 135deg,
  102. rgba(255, 255, 255, 0.1) 0%,
  103. rgba(255, 255, 255, 0.05) 100%
  104. );
  105. backdrop-filter: blur(10px);
  106. border: 1px solid rgba(255, 255, 255, 0.2);
  107. }
  108. .reward-item {
  109. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  110. color: white;
  111. transition: all 0.3s ease;
  112. }
  113. .reward-item:hover {
  114. transform: scale(1.05);
  115. box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
  116. }
  117. .address-item {
  118. transition: all 0.3s ease;
  119. }
  120. .address-item:hover {
  121. background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
  122. color: white;
  123. transform: translateX(5px);
  124. }
  125. .btn-glow {
  126. position: relative;
  127. overflow: hidden;
  128. }
  129. .btn-glow::before {
  130. content: "";
  131. position: absolute;
  132. top: 0;
  133. left: -100%;
  134. width: 100%;
  135. height: 100%;
  136. background: linear-gradient(
  137. 90deg,
  138. transparent,
  139. rgba(255, 255, 255, 0.4),
  140. transparent
  141. );
  142. transition: left 0.5s;
  143. }
  144. .btn-glow:hover::before {
  145. left: 100%;
  146. }
  147. .item-icon {
  148. width: 100%;
  149. height: 100%;
  150. object-fit: contain;
  151. transition: transform 0.3s ease;
  152. }
  153. .item-icon:hover {
  154. transform: scale(1.1);
  155. }
  156. .icon-container {
  157. display: flex;
  158. align-items: center;
  159. justify-content: center;
  160. background: rgba(255, 255, 255, 0.1);
  161. border: 1px solid rgba(255, 255, 255, 0.2);
  162. border-radius: 50%;
  163. overflow: hidden;
  164. transition: all 0.3s ease;
  165. }
  166. .icon-container:hover {
  167. background: rgba(255, 255, 255, 0.2);
  168. border-color: rgba(255, 255, 255, 0.4);
  169. }
  170. </style>
  171. </head>
  172. <body
  173. class="min-h-screen bg-gradient-to-br from-indigo-900 via-purple-900 to-pink-900"
  174. >
  175. <div id="app" class="container mx-auto p-4 md:p-8">
  176. <!-- Animated Background -->
  177. <div class="fixed inset-0 overflow-hidden pointer-events-none">
  178. <div
  179. 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"
  180. ></div>
  181. <div
  182. 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"
  183. style="animation-delay: 1s"
  184. ></div>
  185. <div
  186. 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"
  187. style="animation-delay: 2s"
  188. ></div>
  189. </div>
  190. <!-- Header -->
  191. <div class="text-center mb-12 relative z-10">
  192. <div class="floating mb-4">
  193. <i class="fas fa-rocket text-6xl text-white mb-4"></i>
  194. </div>
  195. <h1 class="text-5xl font-bold text-white mb-4 neon-glow">
  196. POG Airdrop
  197. </h1>
  198. <p class="text-xl text-purple-200 shimmer">
  199. Mass Reward Distribution System
  200. </p>
  201. <div class="flex justify-center mt-6 space-x-4">
  202. <div class="stat-card rounded-lg p-4 text-white">
  203. <div class="text-2xl font-bold">{{ addressList.length }}</div>
  204. <div class="text-sm opacity-80">Addresses</div>
  205. </div>
  206. <div class="stat-card rounded-lg p-4 text-white">
  207. <div class="text-2xl font-bold">{{ totalRewardCount }}</div>
  208. <div class="text-sm opacity-80">Rewards</div>
  209. </div>
  210. <div class="stat-card rounded-lg p-4 text-white">
  211. <div class="text-2xl font-bold">
  212. {{ totalRewardCount * addressList.length }}
  213. </div>
  214. <div class="text-sm opacity-80">Total</div>
  215. </div>
  216. </div>
  217. </div>
  218. <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 relative z-10">
  219. <!-- Left: Reward Management -->
  220. <div class="space-y-8">
  221. <!-- Reward Management -->
  222. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  223. <div class="p-6">
  224. <div class="flex justify-between items-center mb-6">
  225. <h2 class="text-2xl font-bold text-white">
  226. <i class="fas fa-gift text-purple-300 mr-3"></i>
  227. Reward Management
  228. </h2>
  229. <div class="badge badge-primary badge-lg animate-pulse-slow">
  230. {{ waitAddRewardList.length }} Types
  231. </div>
  232. </div>
  233. <div
  234. v-if="waitAddRewardList.length === 0"
  235. class="text-center py-12"
  236. >
  237. <div class="text-8xl mb-6 animate-bounce-slow">🎁</div>
  238. <p class="text-purple-200 text-lg">No rewards added yet</p>
  239. <p class="text-purple-300 text-sm mt-2">
  240. Click the button below to add rewards
  241. </p>
  242. </div>
  243. <div
  244. v-else
  245. class="space-y-4 max-h-80 overflow-y-auto scrollbar-thin"
  246. >
  247. <div
  248. v-for="reward in waitAddRewardList"
  249. :key="reward.id"
  250. class="reward-item rounded-xl p-4 border border-white/20"
  251. >
  252. <div class="flex justify-between items-center">
  253. <div class="flex items-center space-x-4">
  254. <div class="w-12 h-12 icon-container backdrop-blur-sm">
  255. <img
  256. :src="getItemIcon(reward.id)"
  257. :alt="getItemName(reward.id)"
  258. class="w-8 h-8 item-icon"
  259. @error="handleImageError"
  260. />
  261. </div>
  262. <div>
  263. <div class="font-semibold text-lg">
  264. {{ getItemName(reward.id) }}
  265. </div>
  266. <div class="text-sm opacity-80">
  267. {{ reward.count }} per address
  268. </div>
  269. </div>
  270. </div>
  271. <div class="flex items-center space-x-3">
  272. <input
  273. type="number"
  274. v-model="reward.count"
  275. min="1"
  276. class="input input-bordered input-sm w-20 text-center bg-white/20 border-white/30 text-white placeholder-white/50"
  277. @change="updateRewardCount(reward.id, reward.count)"
  278. />
  279. <button
  280. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  281. @click="removeWaitReward(reward.id)"
  282. >
  283. <i class="fas fa-times"></i>
  284. </button>
  285. </div>
  286. </div>
  287. </div>
  288. </div>
  289. <div class="text-center mt-6">
  290. <button
  291. class="btn btn-primary btn-lg gradient-bg-2 border-0 text-white shadow-lg btn-glow"
  292. @click="showAddRewardDialog = true"
  293. >
  294. <i class="fas fa-plus mr-2"></i>
  295. Add Reward
  296. </button>
  297. </div>
  298. </div>
  299. </div>
  300. <!-- Send Preview -->
  301. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  302. <div class="p-6">
  303. <h2 class="text-2xl font-bold text-white mb-6">
  304. <i class="fas fa-chart-line text-blue-300 mr-3"></i>
  305. Send Preview
  306. </h2>
  307. <!-- Statistics -->
  308. <div class="grid grid-cols-3 gap-4 mb-8">
  309. <div class="stat-card rounded-xl p-4 text-center">
  310. <div class="text-3xl font-bold text-white mb-1">
  311. {{ addressList.length }}
  312. </div>
  313. <div class="text-sm text-purple-200">Addresses</div>
  314. </div>
  315. <div class="stat-card rounded-xl p-4 text-center">
  316. <div class="text-3xl font-bold text-white mb-1">
  317. {{ totalRewardCount }}
  318. </div>
  319. <div class="text-sm text-purple-200">Rewards</div>
  320. </div>
  321. <div class="stat-card rounded-xl p-4 text-center">
  322. <div class="text-3xl font-bold text-white mb-1">
  323. {{ totalRewardCount * addressList.length }}
  324. </div>
  325. <div class="text-sm text-purple-200">Total</div>
  326. </div>
  327. </div>
  328. <!-- Item Summary -->
  329. <div v-if="waitAddRewardList.length > 0">
  330. <h3 class="text-xl font-semibold text-white mb-4">
  331. Item Summary
  332. </h3>
  333. <div class="space-y-3 max-h-60 overflow-y-auto scrollbar-thin">
  334. <div
  335. v-for="reward in waitAddRewardList"
  336. :key="reward.id"
  337. class="flex justify-between items-center p-3 bg-white/10 rounded-lg backdrop-blur-sm"
  338. >
  339. <div class="flex items-center space-x-3">
  340. <div class="w-8 h-8 icon-container">
  341. <img
  342. :src="getItemIcon(reward.id)"
  343. :alt="getItemName(reward.id)"
  344. class="w-6 h-6 item-icon"
  345. @error="handleImageError"
  346. />
  347. </div>
  348. <span class="font-medium text-white"
  349. >{{ getItemName(reward.id) }}</span
  350. >
  351. </div>
  352. <div class="flex items-center space-x-2">
  353. <span class="text-sm text-purple-200"
  354. >{{ reward.count }} × {{ addressList.length }}</span
  355. >
  356. <span class="font-bold text-yellow-300"
  357. >= {{ reward.count * addressList.length }}</span
  358. >
  359. </div>
  360. </div>
  361. </div>
  362. </div>
  363. </div>
  364. </div>
  365. </div>
  366. <!-- Right: Address Management -->
  367. <div class="space-y-8">
  368. <!-- Address Input -->
  369. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  370. <div class="p-6">
  371. <h2 class="text-2xl font-bold text-white mb-6">
  372. <i class="fas fa-users text-green-300 mr-3"></i>
  373. Address Management
  374. </h2>
  375. <div class="form-control">
  376. <label class="label">
  377. <span class="label-text font-medium text-white"
  378. >Wallet Address List</span
  379. >
  380. <span class="label-text-alt text-purple-300"
  381. >{{ addressList.length }} addresses</span
  382. >
  383. </label>
  384. <textarea
  385. v-model="waitInputAddresses"
  386. rows="8"
  387. placeholder="Enter wallet addresses, one per line&#10;Example:&#10;0x1234567890abcdef...&#10;0xabcdef1234567890..."
  388. class="textarea textarea-bordered w-full font-mono text-sm bg-white/10 border-white/30 text-white placeholder-white/50"
  389. @input="parseAddresses"
  390. ></textarea>
  391. <label class="label">
  392. <span class="label-text-alt text-purple-200"
  393. >Support batch paste, one address per line</span
  394. >
  395. </label>
  396. </div>
  397. <!-- Quick Actions -->
  398. <div class="flex flex-wrap gap-2 mt-6">
  399. <button
  400. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  401. @click="clearAddresses"
  402. >
  403. <i class="fas fa-trash mr-1"></i> Clear
  404. </button>
  405. <button
  406. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  407. @click="exportAddresses"
  408. >
  409. <i class="fas fa-download mr-1"></i> Export
  410. </button>
  411. <button
  412. class="btn btn-sm btn-outline border-white/30 text-white hover:bg-white/10"
  413. @click="importAddresses"
  414. >
  415. <i class="fas fa-upload mr-1"></i> Import
  416. </button>
  417. </div>
  418. </div>
  419. </div>
  420. <!-- Address List -->
  421. <div class="glass-effect rounded-2xl shadow-2xl card-hover">
  422. <div class="p-6">
  423. <div class="flex justify-between items-center mb-6">
  424. <h2 class="text-2xl font-bold text-white">
  425. <i class="fas fa-list text-orange-300 mr-3"></i>
  426. Address List
  427. </h2>
  428. <div class="flex items-center space-x-3">
  429. <input
  430. type="text"
  431. v-model="searchAddress"
  432. placeholder="Search addresses..."
  433. class="input input-bordered input-sm w-48 bg-white/10 border-white/30 text-white placeholder-white/50"
  434. />
  435. <div class="badge badge-outline border-white/30 text-white">
  436. {{ filteredAddresses.length }}/{{ addressList.length }}
  437. </div>
  438. </div>
  439. </div>
  440. <div v-if="addressList.length === 0" class="text-center py-12">
  441. <div class="text-8xl mb-6 animate-bounce-slow">👤</div>
  442. <p class="text-purple-200 text-lg">No addresses yet</p>
  443. <p class="text-purple-300 text-sm mt-2">
  444. Please enter addresses above
  445. </p>
  446. </div>
  447. <div v-else class="max-h-80 overflow-y-auto scrollbar-thin">
  448. <div
  449. v-for="(address, index) in filteredAddresses"
  450. :key="index"
  451. class="address-item flex justify-between items-center p-3 rounded-lg mb-2 bg-white/5 backdrop-blur-sm"
  452. >
  453. <div class="flex items-center space-x-4">
  454. <div
  455. 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"
  456. >
  457. {{ index + 1 }}
  458. </div>
  459. <div class="font-mono text-sm text-white">
  460. {{ address }}
  461. </div>
  462. </div>
  463. <button
  464. class="btn btn-circle btn-sm bg-red-500 border-0 hover:bg-red-600"
  465. @click="removeAddress(index)"
  466. >
  467. <i class="fas fa-times"></i>
  468. </button>
  469. </div>
  470. </div>
  471. </div>
  472. </div>
  473. </div>
  474. </div>
  475. <!-- Bottom Send Button -->
  476. <div class="mt-12 text-center relative z-10">
  477. <button
  478. class="btn btn-primary btn-xl gradient-bg-3 border-0 text-white shadow-2xl btn-glow neon-glow"
  479. @click="sendAllRewards"
  480. :disabled="addressList.length === 0 || waitAddRewardList.length === 0"
  481. >
  482. <i class="fas fa-rocket mr-3 animate-bounce-slow"></i>
  483. Launch Airdrop ({{ totalRewardCount * addressList.length }} rewards)
  484. </button>
  485. </div>
  486. <!-- Add Reward Modal -->
  487. <div class="modal" :class="{ 'modal-open': showAddRewardDialog }">
  488. <div class="modal-box max-w-md glass-effect border border-white/20">
  489. <h3 class="font-bold text-2xl mb-6 text-white">
  490. <i class="fas fa-gift text-purple-300 mr-2"></i>
  491. Add Reward
  492. </h3>
  493. <div class="space-y-6">
  494. <div class="form-control">
  495. <label class="label">
  496. <span class="label-text font-medium text-white"
  497. >Select Item</span
  498. >
  499. </label>
  500. <div
  501. class="grid grid-cols-2 gap-2 max-h-48 overflow-y-auto scrollbar-thin"
  502. >
  503. <div
  504. v-for="item in staticGoods"
  505. :key="item.id"
  506. @click="selectedReward = item.id"
  507. :class="[
  508. 'p-3 rounded-lg border-2 cursor-pointer transition-all duration-200',
  509. selectedReward == item.id
  510. ? 'border-purple-400 bg-purple-500/20'
  511. : 'border-white/20 bg-white/5 hover:bg-white/10'
  512. ]"
  513. >
  514. <div class="flex items-center space-x-3">
  515. <div class="w-8 h-8 icon-container">
  516. <img
  517. :src="getItemIcon(item.id)"
  518. :alt="item.name"
  519. class="w-6 h-6 item-icon"
  520. @error="handleImageError"
  521. />
  522. </div>
  523. <div>
  524. <div class="font-medium text-white text-sm">
  525. {{ item.name }}
  526. </div>
  527. <div class="text-xs text-purple-200">
  528. Stock: {{ item.count }}
  529. </div>
  530. </div>
  531. </div>
  532. </div>
  533. </div>
  534. </div>
  535. <div class="form-control">
  536. <label class="label">
  537. <span class="label-text font-medium text-white"
  538. >Quantity per Address</span
  539. >
  540. </label>
  541. <input
  542. type="number"
  543. v-model="rewardQuantity"
  544. min="1"
  545. class="input input-bordered w-full bg-white/10 border-white/30 text-white placeholder-white/50"
  546. placeholder="Enter quantity"
  547. />
  548. </div>
  549. </div>
  550. <div class="modal-action">
  551. <button
  552. class="btn btn-primary gradient-bg-2 border-0 text-white"
  553. @click="addRewardConfirm"
  554. :disabled="!selectedReward || !rewardQuantity || rewardQuantity <= 0"
  555. >
  556. <i class="fas fa-check mr-2"></i>
  557. Add Reward
  558. </button>
  559. <button class="btn btn-ghost text-white" @click="closeModal">
  560. Cancel
  561. </button>
  562. </div>
  563. </div>
  564. <div class="modal-backdrop" @click="closeModal"></div>
  565. </div>
  566. <!-- Toast Container -->
  567. <div class="toast toast-top toast-end">
  568. <div v-if="toast.show" :class="['alert', toast.type, 'glass-effect']">
  569. <div>
  570. <span class="text-white">{{ toast.message }}</span>
  571. </div>
  572. </div>
  573. </div>
  574. </div>
  575. <script>
  576. const { createApp } = Vue;
  577. const API_BASE_URL = "http://54.169.180.142:10234";
  578. const Toast = {
  579. template: `
  580. <div class="toast toast-top toast-end">
  581. <div v-if="show" :class="['alert', type, 'glass-effect']">
  582. <div>
  583. <span class="text-white">{{ message }}</span>
  584. </div>
  585. </div>
  586. </div>
  587. `,
  588. props: ["show", "type", "message"],
  589. };
  590. createApp({
  591. data() {
  592. return {
  593. waitInputAddresses: "",
  594. addressList: [],
  595. waitAddRewardList: [],
  596. showAddRewardDialog: false,
  597. selectedReward: "",
  598. rewardQuantity: "",
  599. searchAddress: "",
  600. staticGoods: [],
  601. toast: {
  602. show: false,
  603. type: "alert-info",
  604. message: "",
  605. },
  606. };
  607. },
  608. computed: {
  609. totalRewardCount() {
  610. return this.waitAddRewardList.reduce(
  611. (total, reward) => total + reward.count,
  612. 0
  613. );
  614. },
  615. filteredAddresses() {
  616. if (!this.searchAddress) return this.addressList;
  617. return this.addressList.filter((addr) =>
  618. addr.toLowerCase().includes(this.searchAddress.toLowerCase())
  619. );
  620. },
  621. },
  622. components: {
  623. Toast,
  624. },
  625. methods: {
  626. // Parse addresses
  627. parseAddresses() {
  628. if (this.waitInputAddresses.trim()) {
  629. const addresses = this.waitInputAddresses
  630. .split("\n")
  631. .map((addr) => addr.trim())
  632. .filter((addr) => addr && addr.length > 0);
  633. this.addressList = addresses;
  634. } else {
  635. this.addressList = [];
  636. }
  637. },
  638. // Remove address
  639. removeAddress(index) {
  640. this.addressList.splice(index, 1);
  641. this.updateAddressInput();
  642. this.showToast(`Address removed`, "alert-info");
  643. },
  644. // Update address input
  645. updateAddressInput() {
  646. this.waitInputAddresses = this.addressList.join("\n");
  647. },
  648. // Clear addresses
  649. clearAddresses() {
  650. this.addressList = [];
  651. this.waitInputAddresses = "";
  652. this.showToast("All addresses cleared", "alert-info");
  653. },
  654. // Export addresses
  655. exportAddresses() {
  656. if (this.addressList.length === 0) {
  657. this.showToast("No addresses to export", "alert-error");
  658. return;
  659. }
  660. const text = this.addressList.join("\n");
  661. const blob = new Blob([text], { type: "text/plain" });
  662. const url = URL.createObjectURL(blob);
  663. const a = document.createElement("a");
  664. a.href = url;
  665. a.download = `addresses_${
  666. new Date().toISOString().split("T")[0]
  667. }.txt`;
  668. a.click();
  669. URL.revokeObjectURL(url);
  670. this.showToast(
  671. `Exported ${this.addressList.length} addresses`,
  672. "alert-success"
  673. );
  674. },
  675. // Import addresses
  676. importAddresses() {
  677. const input = document.createElement("input");
  678. input.type = "file";
  679. input.accept = ".txt,.csv";
  680. input.onchange = (e) => {
  681. const file = e.target.files[0];
  682. if (file) {
  683. const reader = new FileReader();
  684. reader.onload = (e) => {
  685. const content = e.target.result;
  686. this.waitInputAddresses = content;
  687. this.parseAddresses();
  688. this.showToast(
  689. `Imported ${this.addressList.length} addresses`,
  690. "alert-success"
  691. );
  692. };
  693. reader.readAsText(file);
  694. }
  695. };
  696. input.click();
  697. },
  698. // Add reward confirmation
  699. addRewardConfirm() {
  700. if (
  701. this.selectedReward &&
  702. this.rewardQuantity &&
  703. this.rewardQuantity > 0
  704. ) {
  705. const itemId = parseInt(this.selectedReward);
  706. const count = parseInt(this.rewardQuantity);
  707. // Check if item already exists
  708. const existingIndex = this.waitAddRewardList.findIndex(
  709. (item) => item.id === itemId
  710. );
  711. if (existingIndex > -1) {
  712. // If exists, increase quantity
  713. this.waitAddRewardList[existingIndex].count += count;
  714. this.showToast(
  715. `Added ${count} more ${this.getItemName(itemId)}`,
  716. "alert-success"
  717. );
  718. } else {
  719. // If not exists, add new item
  720. this.waitAddRewardList.push({
  721. id: itemId,
  722. count: count,
  723. });
  724. this.showToast(
  725. `Added ${count} ${this.getItemName(itemId)}`,
  726. "alert-success"
  727. );
  728. }
  729. this.closeModal();
  730. }
  731. },
  732. // Update reward count
  733. updateRewardCount(id, count) {
  734. const reward = this.waitAddRewardList.find(
  735. (item) => item.id === id
  736. );
  737. if (reward && count > 0) {
  738. reward.count = parseInt(count);
  739. this.showToast(
  740. `Updated ${this.getItemName(id)} quantity to ${count}`,
  741. "alert-info"
  742. );
  743. }
  744. },
  745. // Remove reward
  746. removeWaitReward(id) {
  747. const index = this.waitAddRewardList.findIndex(
  748. (item) => item.id === id
  749. );
  750. if (index > -1) {
  751. const removedReward = this.waitAddRewardList.splice(index, 1)[0];
  752. this.showToast(
  753. `Removed ${removedReward.count} ${this.getItemName(id)}`,
  754. "alert-info"
  755. );
  756. }
  757. },
  758. // Get item name
  759. getItemName(id) {
  760. const item = this.staticGoods.find((item) => item.id == id);
  761. return item ? item.name : `Unknown Item (${id})`;
  762. },
  763. // Get item icon
  764. getItemIcon(id) {
  765. const item = this.staticGoods.find((item) => item.id == id);
  766. if (item && item.icon) {
  767. return `img/${item.icon}.png`;
  768. }
  769. return "img/default_icon.png";
  770. },
  771. // Handle image error
  772. handleImageError(event) {
  773. // Replace with fallback icon or hide the image
  774. event.target.style.display = "none";
  775. const parent = event.target.parentElement;
  776. if (parent) {
  777. parent.innerHTML =
  778. '<i class="fas fa-question text-white/50"></i>';
  779. }
  780. },
  781. // Send all rewards
  782. sendAllRewards() {
  783. if (this.addressList.length === 0) {
  784. this.showToast(
  785. "Please add wallet addresses first",
  786. "alert-error"
  787. );
  788. return;
  789. }
  790. if (this.waitAddRewardList.length === 0) {
  791. this.showToast("Please add rewards first", "alert-error");
  792. return;
  793. }
  794. // Build send data
  795. const sendData = {
  796. addresses: this.addressList,
  797. rewards: this.waitAddRewardList,
  798. totalAddresses: this.addressList.length,
  799. totalRewards: this.totalRewardCount,
  800. rewardTypes: this.waitAddRewardList.length,
  801. };
  802. console.log("Send data:", sendData);
  803. // Here you can add actual API call
  804. // Example: call send API
  805. this.showToast(
  806. `Preparing to send ${this.totalRewardCount} rewards to ${this.addressList.length} addresses`,
  807. "alert-info"
  808. );
  809. // Simulate sending process
  810. setTimeout(() => {
  811. this.showToast(
  812. "Airdrop completed successfully!",
  813. "alert-success"
  814. );
  815. // You can clear lists or keep data after sending
  816. }, 2000);
  817. },
  818. closeModal() {
  819. this.showAddRewardDialog = false;
  820. this.selectedReward = "";
  821. this.rewardQuantity = "";
  822. },
  823. showToast(message, type = "alert-info") {
  824. this.toast = {
  825. show: true,
  826. type: type,
  827. message: message,
  828. };
  829. setTimeout(() => {
  830. this.toast.show = false;
  831. }, 3000);
  832. },
  833. },
  834. mounted() {
  835. const m = {
  836. 1: {
  837. name: "POG",
  838. count: 100,
  839. icon: "icon_pog",
  840. },
  841. 2: {
  842. name: "Diamond",
  843. count: 100,
  844. icon: "icon_gem",
  845. },
  846. 3: {
  847. name: "GAME_SHARD",
  848. count: 100,
  849. icon: "icon_puzzle",
  850. },
  851. 5: {
  852. name: "CRIT_CARD",
  853. count: 100,
  854. icon: "icon_crit",
  855. },
  856. 6: {
  857. name: "FREE_ITEM_BOX",
  858. count: 100,
  859. icon: "icon_item_box",
  860. },
  861. 7: {
  862. name: "ITEM_BOX",
  863. count: 100,
  864. icon: "icon_item_box",
  865. },
  866. 8: {
  867. name: "FREE_POG_BOX",
  868. count: 100,
  869. icon: "icon_pog_box",
  870. },
  871. 9: {
  872. name: "POG_BOX",
  873. count: 100,
  874. icon: "icon_pog_box",
  875. },
  876. };
  877. this.staticGoods = [];
  878. for (const key in m) {
  879. this.staticGoods.push({
  880. id: key,
  881. name: m[key].name,
  882. count: m[key].count,
  883. });
  884. }
  885. console.log(this.staticGoods);
  886. },
  887. }).mount("#app");
  888. </script>
  889. </body>
  890. </html>