@necrolab/dashboard 0.5.14 → 0.5.16

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 (120) 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 +140 -170
  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 +58 -15
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +119 -0
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -19
  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 +220 -0
  20. package/src/assets/css/main.scss +72 -75
  21. package/src/components/Auth/LoginForm.vue +5 -84
  22. package/src/components/Editors/Account/Account.vue +8 -10
  23. package/src/components/Editors/Account/AccountCreator.vue +28 -59
  24. package/src/components/Editors/Account/AccountView.vue +38 -86
  25. package/src/components/Editors/Account/CreateAccount.vue +8 -50
  26. package/src/components/Editors/Profile/CreateProfile.vue +74 -131
  27. package/src/components/Editors/Profile/Profile.vue +15 -17
  28. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  29. package/src/components/Editors/Profile/ProfileView.vue +46 -96
  30. package/src/components/Editors/TagLabel.vue +16 -55
  31. package/src/components/Editors/TagToggle.vue +20 -8
  32. package/src/components/Filter/Filter.vue +62 -75
  33. package/src/components/Filter/FilterPreview.vue +161 -135
  34. package/src/components/Filter/PriceSortToggle.vue +36 -43
  35. package/src/components/Table/Header.vue +1 -1
  36. package/src/components/Table/Table.vue +61 -12
  37. package/src/components/Tasks/CheckStock.vue +7 -16
  38. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  39. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  40. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  41. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  42. package/src/components/Tasks/EventDetailRow.vue +21 -0
  43. package/src/components/Tasks/MassEdit.vue +6 -16
  44. package/src/components/Tasks/QuickSettings.vue +140 -216
  45. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  46. package/src/components/Tasks/Stats.vue +19 -38
  47. package/src/components/Tasks/Task.vue +65 -268
  48. package/src/components/Tasks/TaskLabel.vue +9 -3
  49. package/src/components/Tasks/TaskView.vue +43 -63
  50. package/src/components/Tasks/Utilities.vue +10 -42
  51. package/src/components/Tasks/ViewTask.vue +23 -107
  52. package/src/components/icons/Close.vue +2 -8
  53. package/src/components/icons/Gear.vue +8 -8
  54. package/src/components/icons/Hash.vue +5 -0
  55. package/src/components/icons/Key.vue +2 -8
  56. package/src/components/icons/Pencil.vue +2 -8
  57. package/src/components/icons/Profile.vue +2 -8
  58. package/src/components/icons/Sell.vue +2 -8
  59. package/src/components/icons/Spinner.vue +4 -7
  60. package/src/components/icons/SquareCheck.vue +2 -8
  61. package/src/components/icons/SquareUncheck.vue +2 -8
  62. package/src/components/icons/Wildcard.vue +2 -8
  63. package/src/components/icons/index.js +3 -1
  64. package/src/components/ui/ActionButtonGroup.vue +113 -52
  65. package/src/components/ui/BalanceIndicator.vue +60 -0
  66. package/src/components/ui/EmptyState.vue +24 -0
  67. package/src/components/ui/EnableDisableToggle.vue +23 -0
  68. package/src/components/ui/FormField.vue +48 -48
  69. package/src/components/ui/IconLabel.vue +23 -0
  70. package/src/components/ui/InfoRow.vue +21 -54
  71. package/src/components/ui/Modal.vue +78 -37
  72. package/src/components/ui/Navbar.vue +60 -41
  73. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  74. package/src/components/ui/ReconnectIndicator.vue +111 -124
  75. package/src/components/ui/SectionCard.vue +6 -14
  76. package/src/components/ui/Splash.vue +2 -10
  77. package/src/components/ui/StatusBadge.vue +26 -28
  78. package/src/components/ui/TaskToggle.vue +54 -0
  79. package/src/components/ui/controls/CountryChooser.vue +27 -64
  80. package/src/components/ui/controls/EyeToggle.vue +1 -1
  81. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  82. package/src/components/ui/controls/atomic/Dropdown.vue +102 -95
  83. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -94
  84. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  85. package/src/composables/useColorMapping.js +15 -0
  86. package/src/composables/useCopyToClipboard.js +1 -1
  87. package/src/composables/useDateFormatting.js +21 -0
  88. package/src/composables/useDeviceDetection.js +14 -0
  89. package/src/composables/useDropdownPosition.js +5 -6
  90. package/src/composables/useDynamicTableHeight.js +31 -0
  91. package/src/composables/useRowSelection.js +0 -3
  92. package/src/composables/useTicketPricing.js +16 -0
  93. package/src/composables/useWindowDimensions.js +21 -0
  94. package/src/libs/Filter.js +14 -20
  95. package/src/libs/panzoom.js +1 -5
  96. package/src/libs/utils/array.js +60 -0
  97. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  98. package/src/libs/utils/eventUrl.js +40 -0
  99. package/src/libs/utils/string.js +28 -0
  100. package/src/libs/utils/time.js +20 -0
  101. package/src/libs/utils/validation.js +88 -0
  102. package/src/main.js +0 -2
  103. package/src/stores/connection.js +1 -4
  104. package/src/stores/logger.js +6 -12
  105. package/src/stores/sampleData.js +1 -2
  106. package/src/stores/ui.js +59 -36
  107. package/src/views/Accounts.vue +17 -31
  108. package/src/views/Console.vue +76 -176
  109. package/src/views/Editor.vue +217 -383
  110. package/src/views/FilterBuilder.vue +190 -373
  111. package/src/views/Login.vue +3 -28
  112. package/src/views/Profiles.vue +12 -22
  113. package/src/views/Tasks.vue +51 -38
  114. package/tailwind.config.js +82 -71
  115. package/workbox-config.cjs +47 -5
  116. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2416
  117. package/exit +0 -209
  118. package/run +0 -177
  119. package/switch-branch.sh +0 -41
  120. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,9 +1,9 @@
1
1
  <template>
2
- <div class="modal-mask fixed inset-0 z-modal bg-overlay-dark backdrop-blur-xs flex pt-14 scrollable overflow-y-auto" role="dialog" @touchmove.stop>
3
- <div class="component-modal mb-8 mobile-portrait:mb-16" ref="target">
2
+ <div class="modal-mask" role="dialog" aria-modal="true" @touchmove.stop>
3
+ <div class="modal-content" ref="target">
4
4
  <div class="modal-header">
5
5
  <slot name="header" />
6
- <button @click="ui.toggleModal()" class="btn-icon border-none hover:bg-dark-400">
6
+ <button @click="ui.toggleModal()" class="btn-icon border-none hover:bg-dark-400 ml-auto" aria-label="Close modal">
7
7
  <CloseIcon />
8
8
  </button>
9
9
  </div>
@@ -54,65 +54,106 @@ onClickOutside(target, (event) => {
54
54
  if (event.target.classList.contains("modal-mask")) ui.toggleModal();
55
55
  });
56
56
  </script>
57
- <style lang="scss" scoped>
57
+ <style scoped>
58
58
  .modal-mask {
59
- @apply w-screen duration-300 ease-in-out;
60
- align-items: flex-start;
61
- justify-content: center;
62
- padding: 1rem;
59
+ @apply fixed left-0 top-0;
60
+ @apply flex w-screen overflow-y-auto;
61
+ @apply items-start justify-center;
62
+ @apply p-4 pt-14;
63
+ @apply duration-300 ease-in-out;
64
+ @apply z-modal-mask;
65
+
66
+ /* Background with opacity */
67
+ background-color: rgba(0, 0, 0, 0.85);
68
+ backdrop-filter: blur(8px);
69
+
70
+ /* Height with modern viewport units and fallback */
63
71
  height: 100dvh;
64
- overflow-y: auto;
72
+
73
+ /* Touch handling for mobile */
65
74
  -webkit-overflow-scrolling: touch;
66
75
  touch-action: pan-y !important;
76
+ }
67
77
 
68
- @supports not (height: 100dvh) {
69
- height: 100vh;
78
+ @supports (height: 100dvh) {
79
+ .modal-mask {
80
+ height: 100dvh;
70
81
  }
71
82
  }
72
83
 
73
- .component-modal {
74
- width: 640px;
75
- overflow-y: visible;
76
- @apply flex flex-col rounded-lg bg-dark-300 px-5 py-5;
84
+ @supports not (height: 100dvh) {
85
+ .modal-mask {
86
+ height: 100vh;
87
+ }
88
+ }
77
89
 
78
- .modal-header {
79
- @apply flex font-bold text-white;
90
+ /* Tablet breakpoint */
91
+ @media (max-width: 810px) {
92
+ .modal-mask {
93
+ @apply items-start justify-center;
94
+ @apply p-4 pt-12;
95
+ padding-bottom: 15rem !important;
96
+ }
97
+ }
80
98
 
81
- button {
82
- @apply ml-auto;
83
- }
99
+ /* Mobile portrait breakpoint */
100
+ @media (max-width: 480px) and (orientation: portrait) {
101
+ .modal-mask {
102
+ padding-bottom: 3rem !important;
84
103
  }
104
+ }
85
105
 
86
- .modal-body {
87
- @apply flex flex-col pb-6 md:pb-4;
88
- flex: 1;
106
+ .modal-content {
107
+ @apply flex flex-col rounded-lg;
108
+ @apply bg-dark-400 px-5 py-5;
109
+ @apply w-160 mb-80;
110
+ @apply overflow-y-visible;
111
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8),
112
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
113
+ }
114
+
115
+ /* Mobile breakpoint for modal-content */
116
+ @media (max-width: 480px) {
117
+ .modal-content {
118
+ margin-bottom: 25rem;
89
119
  }
90
120
  }
91
121
 
122
+ /* Tablet breakpoint for modal-content */
92
123
  @media (max-width: 810px) {
93
- .modal-mask {
94
- align-items: flex-start;
95
- justify-content: center;
96
- padding: 1rem;
97
- padding-top: 3rem;
124
+ .modal-content {
125
+ width: calc(100vw - 2rem);
126
+ margin-bottom: 10rem;
98
127
  }
128
+ }
99
129
 
100
- .component-modal {
101
- width: calc(100vw - 2rem);
102
- margin-bottom: 3rem;
130
+ /* Mobile portrait overrides */
131
+ @media (max-width: 480px) and (orientation: portrait) {
132
+ .modal-content {
133
+ margin-bottom: 3rem !important;
134
+ max-height: none !important;
103
135
  }
136
+ }
137
+
138
+ .modal-header {
139
+ @apply flex font-bold text-white;
140
+ }
104
141
 
142
+ .modal-body {
143
+ @apply flex flex-col flex-1;
144
+ }
145
+
146
+ /* Tablet breakpoint for modal-body */
147
+ @media (max-width: 810px) {
105
148
  .modal-body {
106
- overflow-y: visible;
107
- flex: 1;
108
- min-height: 0;
149
+ @apply pb-16 overflow-y-visible min-h-0;
109
150
  }
110
151
  }
111
152
 
112
- /* iPhone portrait mode - extra spacing for create button */
153
+ /* Mobile portrait breakpoint for modal-body */
113
154
  @media (max-width: 480px) and (orientation: portrait) {
114
- .component-modal {
115
- max-height: none;
155
+ .modal-body {
156
+ padding-bottom: 1rem !important;
116
157
  }
117
158
  }
118
159
  </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="navbar" :class="{ 'z-max': menuOpen }">
3
- <div :class="['component-container ios-wrapper flex items-center relative px-2 sm:px-3 lg:px-4', { 'ios-wrapper': landscapeIos }]">
2
+ <div class="navbar" :class="{ 'force-z': menuOpen }">
3
+ <div :class="['component-container ios-wrapper flex items-center relative px-3 lg:px-4', { 'ios-wrapper': landscapeIos }]">
4
4
  <router-link to="/">
5
5
  <img src="@/assets/img/logo_trans.png" class="h-6 lg:h-8 mr-4 z-30 object-cover cursor-pointer" alt="Logo: Necro" />
6
6
  </router-link>
@@ -31,7 +31,7 @@
31
31
  </li>
32
32
  </ul>
33
33
 
34
- <button class="hidden lg:block ml-auto mr-4 smooth-hover" @click="logout()">
34
+ <button class="hidden lg:block ml-auto mr-4 smooth-hover" @click="logout()" aria-label="Logout">
35
35
  <LogoutIcon />
36
36
  </button>
37
37
  <h4 v-if="ui.profile?.name" class="hidden lg:block text-white text-sm font-medium">
@@ -50,7 +50,7 @@
50
50
  <div v-else class="h-10 w-10 rounded-full hidden lg:block mx-4 bg-dark-400" />
51
51
  <CountryChooser class="hidden lg:block" />
52
52
 
53
- <button class="flex lg:hidden ml-auto z-30" @click="toggleMenu">
53
+ <button class="flex lg:hidden ml-auto z-30" @click="toggleMenu" aria-label="Toggle navigation menu" :aria-expanded="menuOpen">
54
54
  <MenuIcon />
55
55
  </button>
56
56
  </div>
@@ -85,7 +85,7 @@
85
85
  </ul>
86
86
  <CountryChooser class="mx-auto block landscape:hidden mb-auto" />
87
87
  <div class="flex mx-auto items-center landscape:mb-0">
88
- <button class="mr-4" @click="logout()">
88
+ <button class="mr-4" @click="logout()" aria-label="Logout">
89
89
  <LogoutIcon />
90
90
  </button>
91
91
  <h4 class="text-white text-sm font-medium mr-4">
@@ -111,15 +111,19 @@
111
111
  </template>
112
112
  <style lang="scss" scoped>
113
113
  .navbar {
114
- @apply border-b py-5 fixed w-full bg-dark-300/95 backdrop-blur border-dark-600;
114
+ @apply border-b py-5 fixed w-full;
115
115
  top: 0;
116
116
  left: 0;
117
117
  z-index: 1000;
118
+ background: theme('colors.dark.300 / 0.95');
119
+ backdrop-filter: blur(23px);
120
+ -webkit-backdrop-filter: blur(23px);
121
+ border-color: theme('colors.border');
118
122
 
119
123
  // Consistent padding base
120
124
  padding-top: 1.25rem;
121
125
  padding-bottom: 1.25rem;
122
-
126
+
123
127
  // Only add safe area top padding for portrait notch devices
124
128
  @supports (padding-top: env(safe-area-inset-top)) {
125
129
  @media (max-device-width: 430px) and (orientation: portrait) {
@@ -135,7 +139,7 @@
135
139
  left: 0;
136
140
  right: 0;
137
141
  height: 100px;
138
- background: oklch(0.1822 0 0);
142
+ background: theme('colors.dark.300');
139
143
  backdrop-filter: blur(8px);
140
144
  -webkit-backdrop-filter: blur(8px);
141
145
  z-index: -1;
@@ -144,27 +148,38 @@
144
148
  ul {
145
149
  @apply gap-x-4;
146
150
 
151
+ // Global SVG normalization - force all icons to be identical
152
+ svg {
153
+ width: 20px !important;
154
+ height: 20px !important;
155
+ display: block !important;
156
+ flex-shrink: 0 !important;
157
+ box-sizing: border-box !important;
158
+ }
159
+
147
160
  li a {
148
- @apply flex text-white text-sm items-center rounded-lg h-10 border-b-2 border-transparent;
161
+ @apply flex text-white text-sm items-center rounded-lg;
162
+ height: 40px;
163
+ border: 1px solid transparent;
149
164
  border-left: 3px solid transparent;
150
165
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
151
166
  position: relative;
152
167
 
153
168
  svg {
154
- @apply mr-2 w-5 h-5;
169
+ @apply mr-2;
155
170
  }
156
171
 
157
172
  &.router-link-exact-active {
158
- border-bottom: 2px solid oklch(0.72 0.15 145);
159
- color: oklch(0.72 0.15 145);
160
- background: oklch(0.72 0.15 145 / 0.08);
173
+ border-bottom: 2px solid theme('colors.primary');
174
+ color: theme('colors.primary');
175
+ background: theme('colors.primary / 0.08');
161
176
  margin-bottom: -2px;
162
177
 
163
178
  // Mobile styling
164
179
  @media (max-width: 1023px) {
165
- border: 2px solid oklch(0.72 0.15 145);
166
- color: oklch(0.72 0.15 145);
167
- background: oklch(0.72 0.15 145 / 0.08);
180
+ border: 2px solid theme('colors.primary');
181
+ color: theme('colors.primary');
182
+ background: theme('colors.primary / 0.08');
168
183
  padding: 0.5rem 0.75rem;
169
184
  border-radius: 0.5rem;
170
185
  margin-bottom: 0;
@@ -175,26 +190,35 @@
175
190
  // Desktop mode (lg to xl): icon-only view with perfect centering
176
191
  @media (min-width: 1024px) and (max-width: 1279px) {
177
192
  li a {
178
- @apply w-10 h-10 flex items-center justify-center;
179
- padding: 0;
193
+ width: 40px;
194
+ height: 40px;
195
+ padding: 0 !important;
196
+ display: flex !important;
197
+ align-items: center !important;
198
+ justify-content: center !important;
180
199
  position: relative;
181
200
 
182
201
  // Hide text completely in icon-only mode
183
202
  span {
184
- display: none;
203
+ display: none !important;
185
204
  }
186
205
 
187
206
  svg {
188
- @apply m-0 absolute top-1/2 left-1/2;
207
+ margin: 0 !important;
208
+ padding: 0 !important;
209
+ position: absolute;
210
+ top: 50%;
211
+ left: 50%;
189
212
  transform: translate(-50%, -50%);
190
- padding: 0;
191
213
  }
192
214
 
193
215
  &.router-link-exact-active {
194
- @apply border-2 w-10 h-10 rounded-lg;
195
- border-color: oklch(0.72 0.15 145);
196
- color: oklch(0.72 0.15 145);
197
- background: oklch(0.72 0.15 145 / 0.08);
216
+ border: 2px solid theme('colors.primary') !important;
217
+ color: theme('colors.primary') !important;
218
+ background: theme('colors.primary / 0.08') !important;
219
+ border-radius: 0.5rem !important;
220
+ width: 40px !important;
221
+ height: 40px !important;
198
222
  }
199
223
  }
200
224
  }
@@ -202,7 +226,8 @@
202
226
  // XL and above: full width with text
203
227
  @media (min-width: 1280px) {
204
228
  li a {
205
- @apply px-4 h-10;
229
+ @apply px-4;
230
+ height: 40px;
206
231
 
207
232
  svg {
208
233
  @apply mr-2;
@@ -212,6 +237,9 @@
212
237
  }
213
238
  }
214
239
 
240
+ .force-z {
241
+ z-index: 20000;
242
+ }
215
243
 
216
244
  .mobile-menu {
217
245
  margin-top: 0;
@@ -231,17 +259,17 @@
231
259
 
232
260
  .version-badge {
233
261
  @apply flex items-center gap-x-2 px-3 py-2 rounded-lg border;
234
- background: oklch(0.2046 0 0);
235
- border-color: oklch(0.2809 0 0);
262
+ background: theme('colors.bg-elevated');
263
+ border-color: theme('colors.dark.550');
236
264
 
237
265
  .version-label {
238
266
  @apply text-xs font-medium;
239
- color: oklch(0.65 0 0);
267
+ color: theme('colors.text-muted');
240
268
  }
241
269
 
242
270
  .version-number {
243
271
  @apply text-xs font-bold;
244
- color: oklch(0.72 0.15 145);
272
+ color: theme('colors.primary');
245
273
  }
246
274
  }
247
275
  </style>
@@ -263,20 +291,11 @@ import { useUIStore } from "@/stores/ui";
263
291
  import router from "@/router/index";
264
292
  import CountryChooser from "@/components/ui/controls/CountryChooser.vue";
265
293
  import { sendLogout } from "@/stores/requests";
294
+ import { useDeviceDetection } from "@/composables/useDeviceDetection";
266
295
 
267
296
  const landscapeIos = ref(false);
268
297
 
269
- function isIOS() {
270
- if (/iPad|iPhone|iPod/.test(navigator.platform)) {
271
- return true;
272
- } else {
273
- return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
274
- }
275
- }
276
-
277
- function isIpadOS() {
278
- return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
279
- }
298
+ const { isIOS, isIpadOS } = useDeviceDetection();
280
299
 
281
300
  window.matchMedia("(orientation: portrait)").addEventListener("change", (e) => {
282
301
  if (!e.matches && isIOS() && !isIpadOS()) landscapeIos.value = true;
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div class="mt-6 grid grid-cols-12 gap-3 pt-4 border-t border-dark-550">
3
+ <div v-if="data.tags && data.tags.length > 0" class="col-span-6">
4
+ <label class="flex items-center mb-2 text-light-200 font-medium">Tags</label>
5
+ <div class="flex gap-2 flex-wrap">
6
+ <TagLabel v-for="tag in data.tags" :key="tag" :text="tag" />
7
+ </div>
8
+ </div>
9
+ <div class="col-span-6">
10
+ <label class="flex items-center mb-2 text-light-200 font-medium">Status</label>
11
+ <div class="flex items-center gap-3 h-10">
12
+ <StatusBadge :enabled="data.enabled" size="large" />
13
+ <span class="text-sm font-medium" :class="data.enabled ? 'text-green-400' : 'text-red-400'">
14
+ {{ data.enabled ? 'Enabled' : 'Disabled' }}
15
+ </span>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup>
22
+ import StatusBadge from "@/components/ui/StatusBadge.vue";
23
+ import TagLabel from "@/components/Editors/TagLabel.vue";
24
+
25
+ defineProps({
26
+ data: {
27
+ type: Object,
28
+ required: true
29
+ }
30
+ });
31
+ </script>