@necrolab/dashboard 0.5.15 → 0.5.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +70 -566
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +61 -74
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +13 -21
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +295 -0
  20. package/src/assets/css/main.scss +55 -139
  21. package/src/components/Auth/LoginForm.vue +7 -86
  22. package/src/components/Console/ConsoleToolbar.vue +123 -0
  23. package/src/components/Editors/Account/Account.vue +12 -12
  24. package/src/components/Editors/Account/AccountView.vue +38 -111
  25. package/src/components/Editors/Account/CreateAccount.vue +11 -61
  26. package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
  27. package/src/components/Editors/AdminFileEditor.vue +179 -0
  28. package/src/components/Editors/Profile/CreateProfile.vue +77 -150
  29. package/src/components/Editors/Profile/Profile.vue +20 -21
  30. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  31. package/src/components/Editors/Profile/ProfileView.vue +41 -116
  32. package/src/components/Editors/ProxyFileEditor.vue +86 -0
  33. package/src/components/Editors/TagLabel.vue +16 -55
  34. package/src/components/Editors/TagToggle.vue +20 -8
  35. package/src/components/Filter/Filter.vue +66 -79
  36. package/src/components/Filter/FilterPreview.vue +153 -135
  37. package/src/components/Filter/PriceSortToggle.vue +36 -43
  38. package/src/components/Table/Header.vue +1 -1
  39. package/src/components/Table/Table.vue +45 -51
  40. package/src/components/Tasks/CheckStock.vue +7 -16
  41. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  42. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  43. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  44. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  45. package/src/components/Tasks/EventDetailRow.vue +21 -0
  46. package/src/components/Tasks/MassEdit.vue +6 -16
  47. package/src/components/Tasks/QuickSettings.vue +140 -216
  48. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  49. package/src/components/Tasks/Stats.vue +20 -39
  50. package/src/components/Tasks/Task.vue +64 -270
  51. package/src/components/Tasks/TaskLabel.vue +9 -3
  52. package/src/components/Tasks/TaskView.vue +45 -64
  53. package/src/components/Tasks/Utilities.vue +10 -44
  54. package/src/components/Tasks/ViewTask.vue +23 -107
  55. package/src/components/icons/Close.vue +2 -8
  56. package/src/components/icons/Gear.vue +8 -8
  57. package/src/components/icons/Hash.vue +5 -0
  58. package/src/components/icons/Key.vue +2 -8
  59. package/src/components/icons/Pencil.vue +2 -8
  60. package/src/components/icons/Profile.vue +2 -8
  61. package/src/components/icons/Sell.vue +2 -8
  62. package/src/components/icons/Spinner.vue +4 -7
  63. package/src/components/icons/Wildcard.vue +2 -8
  64. package/src/components/icons/index.js +3 -5
  65. package/src/components/ui/ActionButtonGroup.vue +113 -52
  66. package/src/components/ui/BalanceIndicator.vue +60 -0
  67. package/src/components/ui/EmptyState.vue +24 -0
  68. package/src/components/ui/EnableDisableToggle.vue +23 -0
  69. package/src/components/ui/FormField.vue +49 -49
  70. package/src/components/ui/IconLabel.vue +23 -0
  71. package/src/components/ui/InfoRow.vue +21 -54
  72. package/src/components/ui/Modal.vue +161 -54
  73. package/src/components/ui/Navbar.vue +63 -44
  74. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  75. package/src/components/ui/ReconnectIndicator.vue +111 -124
  76. package/src/components/ui/SectionCard.vue +6 -14
  77. package/src/components/ui/Splash.vue +2 -10
  78. package/src/components/ui/StatusBadge.vue +26 -28
  79. package/src/components/ui/TaskToggle.vue +54 -0
  80. package/src/components/ui/controls/CountryChooser.vue +29 -66
  81. package/src/components/ui/controls/EyeToggle.vue +1 -1
  82. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  83. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  84. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
  85. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  86. package/src/composables/useCodeEditor.js +117 -0
  87. package/src/composables/useColorMapping.js +15 -0
  88. package/src/composables/useCopyToClipboard.js +1 -1
  89. package/src/composables/useDateFormatting.js +21 -0
  90. package/src/composables/useDeviceDetection.js +14 -0
  91. package/src/composables/useDropdownPosition.js +1 -4
  92. package/src/composables/useDynamicTableHeight.js +31 -0
  93. package/src/composables/useEnableDisable.js +6 -0
  94. package/src/composables/useFilterCSS.js +71 -0
  95. package/src/composables/useFormValidation.js +92 -0
  96. package/src/composables/useGetAllTags.js +9 -0
  97. package/src/composables/useIOSViewportHandling.js +76 -0
  98. package/src/composables/useNotchHandling.js +306 -0
  99. package/src/composables/useRowSelection.js +0 -3
  100. package/src/composables/useTableRender.js +23 -0
  101. package/src/composables/useTicketPricing.js +16 -0
  102. package/src/composables/useWindowDimensions.js +21 -0
  103. package/src/composables/useZoomPrevention.js +96 -0
  104. package/src/constants/tableLayout.js +14 -0
  105. package/src/libs/Filter.js +14 -20
  106. package/src/libs/panzoom.js +1 -5
  107. package/src/libs/utils/array.js +58 -0
  108. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  109. package/src/libs/utils/eventUrl.js +40 -0
  110. package/src/libs/utils/string.js +3 -0
  111. package/src/libs/utils/time.js +20 -0
  112. package/src/libs/utils/validation.js +64 -0
  113. package/src/main.js +0 -2
  114. package/src/stores/connection.js +1 -29
  115. package/src/stores/logger.js +6 -12
  116. package/src/stores/sampleData.js +1 -2
  117. package/src/stores/ui.js +80 -71
  118. package/src/utils/tableHelpers.js +1 -0
  119. package/src/views/Accounts.vue +19 -38
  120. package/src/views/Console.vue +74 -253
  121. package/src/views/Editor.vue +47 -1114
  122. package/src/views/FilterBuilder.vue +190 -461
  123. package/src/views/Login.vue +3 -28
  124. package/src/views/Profiles.vue +17 -32
  125. package/src/views/Tasks.vue +51 -38
  126. package/tailwind.config.js +82 -71
  127. package/workbox-config.cjs +47 -5
  128. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  129. package/exit +0 -209
  130. package/run +0 -177
  131. package/src/assets/css/base/color-fallbacks.scss +0 -10
  132. package/src/assets/img/background.svg.backup +0 -11
  133. package/src/components/icons/SquareCheck.vue +0 -18
  134. package/src/components/icons/SquareUncheck.vue +0 -18
  135. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  136. package/switch-branch.sh +0 -41
  137. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -9,23 +9,40 @@
9
9
  <div
10
10
  v-for="(value, keyName) in category"
11
11
  :key="keyName"
12
- class="input-container"
12
+ class="form-field-labeled-sm mb-3"
13
13
  >
14
14
  <span class="w-1/3 text-light-300 font-medium text-xs">{{ keyName }}</span>
15
- <div class="w-2/3 h-full flex items-center">
15
+ <div class="w-2/3 h-full relative">
16
16
  <input
17
17
  type="text"
18
- class="quick-settings-input"
18
+ class="api-key-input pr-16"
19
19
  :placeholder="`Enter ${keyName} key`"
20
20
  v-model="quickConfig.keys[categoryName][keyName]"
21
21
  />
22
+ <div class="balance-indicator">
23
+ <div v-if="balanceState[categoryName]?.[keyName]?.loading" class="flex-gap-1 items-center">
24
+ <svg class="animate-spin h-3 w-3 text-light-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
25
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
26
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
27
+ </svg>
28
+ </div>
29
+ <div v-else-if="balanceState[categoryName]?.[keyName]?.error" class="flex-gap-1 items-center" title="Failed to fetch balance">
30
+ <svg class="h-3 w-3 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
32
+ </svg>
33
+ </div>
34
+ <div v-else-if="balanceState[categoryName]?.[keyName]?.balance !== null && balanceState[categoryName]?.[keyName]?.balance !== undefined" class="flex-gap-1 items-center text-green-400" :title="`Balance: ${balanceState[categoryName]?.[keyName]?.balance}`">
35
+ <img src="@/assets/img/sell.svg" width="9" class="opacity-60" />
36
+ <span class="text-2xs font-bold tabular-nums">{{ formatBalance(balanceState[categoryName]?.[keyName]?.balance) }}</span>
37
+ </div>
38
+ </div>
22
39
  </div>
23
40
  </div>
24
41
  </div>
25
42
 
26
43
  <div class="text-light-400 mb-2 mt-4">Proxy Lists</div>
27
44
 
28
- <div class="input-container relative-positioned z-tooltip">
45
+ <div class="form-field-labeled-sm mb-3 relative z-dropdown">
29
46
  <span class="w-1/3 text-light-300 font-medium text-xs">Checkout Proxies</span>
30
47
  <Dropdown
31
48
  class="!w-2/3"
@@ -38,7 +55,7 @@
38
55
  />
39
56
  </div>
40
57
 
41
- <div class="input-container relative-positioned z-dropdown">
58
+ <div class="form-field-labeled-sm mb-3 relative z-dropdown">
42
59
  <span class="w-1/3 text-light-300 font-medium text-xs">Queue Proxies</span>
43
60
  <Dropdown
44
61
  class="!w-2/3"
@@ -50,163 +67,56 @@
50
67
  :allowDefault="false"
51
68
  />
52
69
  </div>
53
- <div class="flex mt-5 justify-between flex-row items-center quicksettings-bottom">
54
- <div class="flex gap-3 items-center quicksettings-versions">
55
- <div class="version-badge">
56
- <span class="version-label">Dashboard</span>
57
- <span class="version-number">v{{ version }}</span>
70
+ <div class="flex mt-5 justify-between flex-row items-center gap-3 flex-col sm:flex-row max-xs:gap-3 max-xs:items-stretch">
71
+ <div class="flex gap-2 items-center max-xs:justify-center max-xs:flex-wrap max-xs:gap-2 max-xs:w-full">
72
+ <div class="badge-version max-xs:flex-1 max-xs:min-w-0 text-center text-2xs">
73
+ <span class="font-medium text-light-500">Dashboard</span>
74
+ <span class="font-bold text-accent-green">v{{ version }}</span>
58
75
  </div>
59
- <div class="version-badge">
60
- <span class="version-label">Bot</span>
61
- <span class="version-number">v{{ ui.botVersion }}</span>
76
+ <div class="badge-version max-xs:flex-1 max-xs:min-w-0 text-center text-2xs">
77
+ <span class="font-medium text-light-500">Bot</span>
78
+ <span class="font-bold text-accent-green">v{{ ui.botVersion }}</span>
62
79
  </div>
63
80
  </div>
64
81
 
65
- <div class="flex gap-3 quicksettings-buttons">
82
+ <div class="flex gap-2 max-xs:w-full max-xs:flex-col max-xs:gap-2">
66
83
  <button
67
- class="quicksettings-btn bg-dark-400 flex items-center justify-center gap-x-2"
84
+ class="btn-modal h-9 text-2xs px-2 max-xs:w-full"
68
85
  @click="checkBalances()"
69
86
  >
70
- Balances <img src="@/assets/img/sell.svg" width="12" />
87
+ Balances <img src="@/assets/img/sell.svg" width="10" class="ml-1" />
71
88
  </button>
72
89
  <button
73
- class="quicksettings-btn bg-dark-400 flex items-center justify-center gap-x-2"
90
+ class="btn-modal h-9 text-2xs px-2 max-xs:w-full"
74
91
  @click="done(true)"
75
92
  >
76
- Save <img src="/img/save.svg" width="12" class="invert" />
93
+ Save <img src="/img/save.svg" width="10" class="ml-1 invert" />
77
94
  </button>
78
95
  </div>
79
96
  </div>
80
97
  </Modal>
81
98
  </template>
82
99
  <style lang="scss">
83
- .input-wrapper {
84
- label {
85
- @apply flex;
86
- }
87
- }
88
-
89
- .transparent {
90
- background: transparent !important;
91
- }
92
-
93
- .input-container {
94
- @apply text-white bg-dark-500 mb-3 px-3 rounded-lg border-dark-550 border-2 flex items-center justify-between h-10;
95
- overflow: visible;
96
- transition: border-color 0.15s ease;
97
-
98
- &:focus-within,
99
- &:has(.dropdown.opened) {
100
- border-color: oklch(0.72 0.15 145) !important;
101
- outline: 1px solid oklch(0.72 0.15 145) !important;
102
- outline-offset: 0;
103
- }
104
- }
105
-
106
- .quick-settings-input {
107
- @apply w-full h-full text-sm text-white bg-transparent border-0 outline-none px-2 py-1 transition-colors duration-150;
108
-
109
- &:focus {
110
- @apply outline-none border-0 shadow-none bg-transparent;
111
- }
112
-
113
- &:hover:not(:focus) {
114
- background: transparent;
115
- }
116
-
117
- &::placeholder {
118
- color: oklch(0.50 0 0);
119
- }
120
- }
121
-
122
- .input-container:hover {
123
- border-color: oklch(0.30 0 0);
124
- }
125
-
126
- .input-container:focus-within {
127
- border-color: oklch(0.72 0.15 145) !important;
128
- outline: 1px solid oklch(0.72 0.15 145);
129
- outline-offset: 0;
130
- }
131
-
132
- /* iPhone portrait mode fixes */
133
- @media (max-width: 430px) and (orientation: portrait) {
134
- .quicksettings-bottom {
135
- flex-direction: column;
136
- gap: 1rem;
137
- align-items: stretch;
138
- }
139
-
140
- .quicksettings-versions {
141
- justify-content: center;
142
- flex-wrap: wrap;
143
- gap: 0.5rem;
144
- }
145
-
146
- .quicksettings-versions > div {
147
- flex: 1;
148
- min-width: 120px;
149
- text-align: center;
150
- }
151
-
152
- .quicksettings-buttons {
153
- justify-content: center;
154
- gap: 0.75rem;
155
- }
156
-
157
- .quicksettings-buttons button {
158
- width: 5rem;
159
- padding-left: 0.5rem;
160
- padding-right: 0.5rem;
161
- gap: 0.25rem;
162
- }
163
-
164
- .quicksettings-buttons button img {
165
- width: 12px;
166
- }
167
- }
168
-
169
- /* Fix modal scrolling on mobile */
100
+ /* Modal mobile scrolling uses :deep() due to teleported element */
170
101
  @media (max-width: 768px) {
171
- :deep(.modal-mask) {
172
- overflow-y: auto !important;
173
- overflow-x: hidden !important;
174
- touch-action: pan-y !important;
175
- -webkit-overflow-scrolling: touch !important;
176
- }
177
-
178
- :deep(.component-modal) {
179
- overflow-y: visible !important;
180
- overflow-x: hidden !important;
181
- touch-action: pan-y !important;
182
- max-height: none !important;
183
- }
102
+ :deep(.modal-mask) { overflow-y: auto !important; overflow-x: hidden !important; touch-action: pan-y !important; -webkit-overflow-scrolling: touch !important; }
103
+ :deep(.component-modal) { overflow-y: visible !important; overflow-x: hidden !important; touch-action: pan-y !important; max-height: none !important; }
184
104
  }
185
105
 
186
- /* Dropdowns are teleported to body, so modal can scroll normally */
187
-
188
- /* Dropdown styling fixes */
189
- :deep(.dropdown) {
106
+ /* Dropdown styling uses :deep() due to teleport to body */
107
+ :deep(.dropdown),
108
+ :deep(.dropdown.opened),
109
+ :deep(.dropdown:focus-within),
110
+ :deep(.dropdown:hover) {
190
111
  background: transparent !important;
191
112
  border: none !important;
192
113
  box-shadow: none !important;
193
- padding: 0 !important;
194
- height: 40px !important;
195
- }
196
-
197
- /* Keep dropdown border hidden when opened (container handles border) */
198
- :deep(.dropdown.opened),
199
- :deep(.dropdown:focus-within) {
200
- border: none !important;
201
114
  outline: none !important;
202
- box-shadow: none !important;
203
115
  }
204
116
 
205
- :deep(.dropdown:hover),
206
- :deep(.dropdown:focus-within) {
207
- border: none !important;
208
- background: transparent !important;
209
- box-shadow: none !important;
117
+ :deep(.dropdown) {
118
+ padding: 0 !important;
119
+ height: 40px !important;
210
120
  }
211
121
 
212
122
  :deep(.dropdown-display) {
@@ -219,6 +129,12 @@
219
129
  line-height: 1.25rem;
220
130
  }
221
131
 
132
+ :deep(.dropdown-item) {
133
+ padding: 0.625rem 1rem;
134
+ font-size: 0.875rem;
135
+ line-height: 1.25rem;
136
+ }
137
+
222
138
  :deep(.dropdown-menu-portal) {
223
139
  z-index: 26000 !important;
224
140
  max-height: 200px !important;
@@ -226,8 +142,8 @@
226
142
  overscroll-behavior: contain !important;
227
143
  touch-action: pan-y !important;
228
144
  -webkit-overflow-scrolling: touch !important;
229
- border: 1px solid oklch(0.26 0 0) !important;
230
- background: linear-gradient(135deg, oklch(0.18 0 0) 0%, oklch(0.20 0 0) 100%) !important;
145
+ @apply border border-dark-550 !important;
146
+ @apply bg-gradient-to-br from-dark-800 to-dark-750 !important;
231
147
  width: calc(100vw - 4rem) !important;
232
148
  max-width: 600px !important;
233
149
 
@@ -236,82 +152,21 @@
236
152
  }
237
153
  }
238
154
 
239
- :deep(.dropdown-item) {
240
- padding: 0.625rem 1rem;
241
- font-size: 0.875rem;
242
- line-height: 1.25rem;
243
- }
244
-
245
- /* Ensure proper z-index stacking */
246
- .z-tooltip {
247
- z-index: 100 !important;
248
- }
249
-
250
- .z-dropdown {
251
- z-index: 90 !important;
252
- }
253
-
254
- /* QuickSettings compact buttons */
255
- .quicksettings-btn {
256
- @apply rounded transition-all duration-150;
257
- background: oklch(0.2046 0 0);
258
- border: 2px solid oklch(0.2809 0 0);
259
- color: oklch(0.90 0 0);
260
- height: 2.5rem;
261
- width: 6.5rem;
262
- font-size: 0.75rem;
263
- font-weight: 500;
155
+ .api-key-input {
156
+ @apply w-full h-full text-sm text-white border-0 rounded-md;
157
+ @apply px-3 py-2;
158
+ @apply focus:outline-none;
159
+ background: oklch(26% 0 68deg);
264
160
 
265
- &:hover {
266
- border-color: oklch(0.72 0.15 145);
267
- }
268
-
269
- &:active, &:focus {
270
- border-color: oklch(0.72 0.15 145);
271
- outline: 1px solid oklch(0.72 0.15 145);
272
- outline-offset: 0;
273
- }
274
-
275
- @media (max-width: 640px) {
276
- height: 2.25rem;
277
- width: 5rem;
278
- font-size: 0.7rem;
279
- padding: 0 0.5rem;
280
-
281
- img {
282
- width: 10px !important;
283
- }
161
+ &::placeholder {
162
+ @apply text-light-500;
284
163
  }
285
164
  }
286
165
 
287
- /* Version badges */
288
- .version-badge {
289
- @apply flex items-center gap-x-2 px-3 py-2 rounded-lg border;
290
- background: oklch(0.2046 0 0);
291
- border-color: oklch(0.2809 0 0);
292
-
293
- .version-label {
294
- @apply text-xs font-medium;
295
- color: oklch(0.65 0 0);
296
- }
297
-
298
- .version-number {
299
- @apply text-xs font-bold;
300
- color: oklch(0.72 0.15 145);
301
- }
302
-
303
- @media (max-width: 640px) {
304
- padding: 0.375rem 0.625rem;
305
- gap: 0.375rem;
306
-
307
- .version-label {
308
- font-size: 0.625rem;
309
- }
310
-
311
- .version-number {
312
- font-size: 0.625rem;
313
- }
314
- }
166
+ .balance-indicator {
167
+ @apply absolute right-3 top-1/2 -translate-y-1/2;
168
+ @apply flex items-center justify-center;
169
+ @apply pointer-events-none;
315
170
  }
316
171
  </style>
317
172
  <script setup>
@@ -319,15 +174,14 @@ import Modal from "@/components/ui/Modal.vue";
319
174
  import { GearIcon } from "@/components/icons";
320
175
  import { useUIStore } from "@/stores/ui";
321
176
  import { ref } from "vue";
322
- import Switch from "@/components/ui/controls/atomic/Switch.vue";
323
177
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
324
- import { sendQuickConfig, getQuickConfig } from "@/stores/requests";
325
- import { getBalances, sendProxyList, getProxyLists } from "../../stores/requests";
178
+ import { sendQuickConfig, getQuickConfig, getBalances, sendProxyList, getProxyLists } from "@/stores/requests";
326
179
 
327
180
  const version = __APP_VERSION__;
328
181
  const quickConfig = ref({});
329
182
  const ui = useUIStore();
330
183
  const proxyLists = ref(["loading..."]);
184
+ const balanceState = ref({});
331
185
 
332
186
  const loadCurrentQuickConfig = async () => {
333
187
  try {
@@ -367,12 +221,82 @@ const loadProxyLists = async () => {
367
221
  };
368
222
 
369
223
  const checkBalances = async () => {
224
+ // Initialize loading state for all keys
225
+ Object.entries(quickConfig.value.keys || {}).forEach(([categoryName, category]) => {
226
+ if (!balanceState.value[categoryName]) {
227
+ balanceState.value[categoryName] = {};
228
+ }
229
+ Object.keys(category).forEach((keyName) => {
230
+ balanceState.value[categoryName][keyName] = {
231
+ loading: true,
232
+ error: false,
233
+ balance: null
234
+ };
235
+ });
236
+ });
237
+
370
238
  const balances = await getBalances();
371
- if (typeof balances == "object")
239
+
240
+ if (typeof balances === "object") {
372
241
  Object.entries(balances).forEach(([service, balance]) => {
373
- ui.showSuccess(`${service} balance: ${balance}`);
242
+ // Find which category and key this service belongs to
243
+ Object.entries(quickConfig.value.keys || {}).forEach(([categoryName, category]) => {
244
+ Object.keys(category).forEach((keyName) => {
245
+ // Match service name (case-insensitive)
246
+ if (keyName.toLowerCase().includes(service.toLowerCase()) ||
247
+ service.toLowerCase().includes(keyName.toLowerCase())) {
248
+ balanceState.value[categoryName][keyName] = {
249
+ loading: false,
250
+ error: false,
251
+ balance: balance
252
+ };
253
+ }
254
+ });
255
+ });
256
+ });
257
+
258
+ // Mark any keys that didn't get a response as errors
259
+ Object.entries(balanceState.value).forEach(([categoryName, category]) => {
260
+ Object.entries(category).forEach(([keyName, state]) => {
261
+ if (state.loading) {
262
+ balanceState.value[categoryName][keyName] = {
263
+ loading: false,
264
+ error: true,
265
+ balance: null
266
+ };
267
+ }
268
+ });
269
+ });
270
+
271
+ ui.showSuccess("Balances updated");
272
+ } else {
273
+ // Mark all as error
274
+ Object.entries(balanceState.value).forEach(([categoryName, category]) => {
275
+ Object.keys(category).forEach((keyName) => {
276
+ balanceState.value[categoryName][keyName] = {
277
+ loading: false,
278
+ error: true,
279
+ balance: null
280
+ };
281
+ });
374
282
  });
375
- else ui.showError("Could not get balances");
283
+ ui.showError("Could not get balances");
284
+ }
285
+ };
286
+
287
+ const formatBalance = (balance) => {
288
+ if (typeof balance === 'number') {
289
+ // Format large numbers with K, M suffixes if needed
290
+ if (balance >= 1000000) {
291
+ return (balance / 1000000).toFixed(1) + 'M';
292
+ } else if (balance >= 100000) {
293
+ return (balance / 1000).toFixed(0) + 'K';
294
+ } else if (balance >= 10000) {
295
+ return (balance / 1000).toFixed(1) + 'K';
296
+ }
297
+ return balance.toLocaleString();
298
+ }
299
+ return String(balance);
376
300
  };
377
301
 
378
302
  loadProxyLists();
@@ -3,7 +3,7 @@
3
3
  <template #header> Scrape Venue <ScrapeIcon class="ml-4" /></template>
4
4
  <!-- Event ID -->
5
5
  <div class="input-wrapper mt-7 mb-4">
6
- <label class="label-override mb-2">Event ID or URL <StadiumIcon /></label>
6
+ <label class="label-override mb-2 flex">Event ID or URL <StadiumIcon /></label>
7
7
  <div class="input-default required">
8
8
  <input
9
9
  :placeholder="!isEU(ui.currentCountry.siteId) ? '102PDA9125510GYU' : '529171'"
@@ -11,27 +11,18 @@
11
11
  />
12
12
  </div>
13
13
  </div>
14
- <button
15
- class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
16
- @click="done()">
14
+ <button class="btn-modal ml-auto mt-4 w-48" @click="done()">
17
15
  Scrape Venue
18
- <ScrapeIcon />
16
+ <ScrapeIcon class="ml-2" />
19
17
  </button>
20
18
  </Modal>
21
19
  </template>
22
- <style lang="scss" scoped>
23
- .input-wrapper {
24
- label {
25
- @apply flex;
26
- }
27
- }
28
- </style>
29
20
  <script setup>
30
21
  import Modal from "@/components/ui/Modal.vue";
31
22
  import { StadiumIcon, ScrapeIcon } from "@/components/icons";
32
23
  import { useUIStore } from "@/stores/ui";
33
24
  import { ref } from "vue";
34
- import { isEU } from "@/stores/utils";
25
+ import { isEU } from "@/libs/utils/eventUrl";
35
26
 
36
27
  const ui = useUIStore();
37
28
  const eventId = ref("");
@@ -1,64 +1,57 @@
1
1
  <template>
2
- <div class="flex items-center gap-1 font-bold text-white lg:gap-3" v-if="ui.queueStats.show" :key="key">
2
+ <div class="flex-gap-1 items-center font-bold text-white lg:gap-3" v-if="ui.queueStats.show" :key="key">
3
3
  <div
4
4
  v-if="ui.queueStats.total"
5
- class="stats-card flex h-10 min-w-0 items-center justify-between gap-2 rounded-lg px-3 text-sm">
6
- <h2 class="flex items-center gap-1 whitespace-nowrap text-sm font-bold">
5
+ class="stat-badge">
6
+ <h2 class="stat-header">
7
7
  <img width="14px" src="@/assets/img/wildcard.svg" />
8
8
  <span class="hidden md:inline">Total</span>
9
9
  </h2>
10
- <span class="flex items-center justify-center whitespace-nowrap text-xs font-black text-light-400">
10
+ <span class="stat-value">
11
11
  {{ ui.queueStats.total }}
12
12
  </span>
13
13
  </div>
14
14
  <div
15
15
  v-if="ui.queueStats.queued"
16
- class="stats-card flex h-10 min-w-0 items-center justify-between gap-2 rounded-lg px-3 text-sm">
17
- <h2 class="flex items-center gap-1 whitespace-nowrap text-sm font-bold">
16
+ class="stat-badge">
17
+ <h2 class="stat-header">
18
18
  <SkiIcon />
19
19
  <span class="hidden md:inline">Queued</span>
20
20
  </h2>
21
- <span class="flex items-center justify-center whitespace-nowrap text-xs font-black text-light-400">
21
+ <span class="stat-value">
22
22
  {{ ui.queueStats.queued }}
23
23
  </span>
24
24
  </div>
25
25
  <div
26
26
  v-if="ui.queueStats.sleeping"
27
- class="stats-card flex h-10 min-w-0 items-center justify-between gap-2 rounded-lg px-3 text-sm">
28
- <h2 class="flex items-center gap-1 whitespace-nowrap text-sm font-bold">
27
+ class="stat-badge">
28
+ <h2 class="stat-header">
29
29
  <TimerIcon />
30
30
  <span class="hidden md:inline">Sleeping</span>
31
31
  </h2>
32
- <span class="flex items-center justify-center whitespace-nowrap text-xs font-black text-light-400">
32
+ <span class="stat-value">
33
33
  {{ ui.queueStats.sleeping }}
34
34
  </span>
35
35
  </div>
36
36
  <div
37
37
  v-if="ui.queueStats.nextQueuePasses.length > 0"
38
- class="stats-card mb-2 flex h-8 min-w-0 items-center justify-between gap-2 rounded-xl p-2 text-sm lg:mb-5 lg:gap-3 lg:p-3">
39
- <h2 class="flex items-center gap-1 whitespace-nowrap text-sm font-bold">
38
+ class="mb-2 flex h-8 min-w-0 items-center justify-between gap-2 rounded-xl p-2 text-sm lg:mb-5 lg:gap-3 lg:p-3 bg-dark-400 border-2 border-dark-550">
39
+ <h2 class="stat-header">
40
40
  <CartIcon />
41
41
  <span class="hidden sm:block">Next Passes</span>
42
42
  <span class="block sm:hidden">Pass</span>
43
43
  </h2>
44
- <span class="flex items-center truncate text-right text-xs font-black text-light-400">
44
+ <span class="flex items-center truncate text-right text-xs font-bold text-light-300">
45
45
  {{ ui.queueStats.nextQueuePasses.slice(0, queuePassAmount).join(", ") }}
46
46
  </span>
47
47
  </div>
48
- <!-- <div
49
- v-if="ui.queueStats.carts"
50
- class="bg-dark-500 mb-2 text-sm rounded-lg lg:p-2 p-1 lg:gap-3 gap-2 flex justify-between"
51
- >
52
- <h2 class="font-bold text-sm flex items-center gap-1"><CartIcon />Carts</h2>
53
- <span class="text-light-400 text-sm font-black flex justify-center">{{ ui.queueStats.sleeping }} </span>
54
- </div> -->
55
48
  </div>
56
49
  </template>
57
50
 
58
51
  <script setup>
59
52
  import { SkiIcon, TimerIcon, CartIcon } from "@/components/icons";
60
53
  import { useUIStore } from "@/stores/ui";
61
- import { ref } from "vue";
54
+ import { ref, onUnmounted } from "vue";
62
55
 
63
56
  const ui = useUIStore();
64
57
 
@@ -71,22 +64,10 @@ const getQueuePassAmount = (width) => {
71
64
  let key = ref(0);
72
65
  let queuePassAmount = ref(getQueuePassAmount(window.innerWidth));
73
66
 
74
- window.addEventListener("resize", () => (queuePassAmount.value = getQueuePassAmount(window.innerWidth)));
75
- </script>
76
- <style lang="scss" scoped>
77
- .stats-card {
78
- @apply rounded;
79
- background: oklch(0.2046 0 0);
80
- border: 2px solid oklch(0.2809 0 0);
81
-
82
- h2 {
83
- font-weight: 600;
84
- color: oklch(0.90 0 0);
85
- }
67
+ const handleResize = () => (queuePassAmount.value = getQueuePassAmount(window.innerWidth));
68
+ window.addEventListener("resize", handleResize);
86
69
 
87
- span {
88
- font-weight: 700;
89
- color: oklch(0.90 0 0);
90
- }
91
- }
92
- </style>
70
+ onUnmounted(() => {
71
+ window.removeEventListener("resize", handleResize);
72
+ });
73
+ </script>