@necrolab/dashboard 0.5.11 → 0.5.13

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 (54) hide show
  1. package/.playwright-mcp/verification-accounts-desktop.png +0 -0
  2. package/.playwright-mcp/verification-tasks-desktop.png +0 -0
  3. package/.playwright-mcp/verification-tasks-mobile.png +0 -0
  4. package/README.md +21 -0
  5. package/docs/plans/2026-02-08-tailwind-consolidation-results.md +476 -0
  6. package/docs/plans/2026-02-08-tailwind-consolidation.md +2416 -0
  7. package/package.json +1 -1
  8. package/src/App.vue +2 -163
  9. package/src/assets/css/components/buttons.scss +43 -95
  10. package/src/assets/css/components/forms.scss +10 -28
  11. package/src/assets/css/components/search-groups.scss +80 -0
  12. package/src/assets/css/components/tables.scss +0 -8
  13. package/src/assets/css/main.scss +29 -43
  14. package/src/components/Editors/Account/Account.vue +14 -220
  15. package/src/components/Editors/Account/AccountCreator.vue +0 -4
  16. package/src/components/Editors/Account/AccountView.vue +0 -1
  17. package/src/components/Editors/Account/CreateAccount.vue +36 -107
  18. package/src/components/Editors/Profile/CreateProfile.vue +46 -135
  19. package/src/components/Editors/Profile/Profile.vue +10 -213
  20. package/src/components/Editors/Profile/ProfileView.vue +0 -1
  21. package/src/components/Filter/Filter.vue +14 -17
  22. package/src/components/Filter/FilterPreview.vue +0 -6
  23. package/src/components/Table/Row.vue +1 -1
  24. package/src/components/Table/Table.vue +2 -16
  25. package/src/components/Tasks/CreateTaskAXS.vue +45 -104
  26. package/src/components/Tasks/CreateTaskTM.vue +58 -96
  27. package/src/components/Tasks/Task.vue +22 -209
  28. package/src/components/Tasks/TaskView.vue +5 -4
  29. package/src/components/Tasks/ViewTask.vue +201 -214
  30. package/src/components/icons/Copy.vue +6 -0
  31. package/src/components/icons/index.js +3 -1
  32. package/src/components/ui/ActionButtonGroup.vue +70 -0
  33. package/src/components/ui/FormField.vue +74 -0
  34. package/src/components/ui/InfoRow.vue +98 -0
  35. package/src/components/ui/Modal.vue +6 -47
  36. package/src/components/ui/Navbar.vue +15 -43
  37. package/src/components/ui/ReconnectIndicator.vue +4 -4
  38. package/src/components/ui/SectionCard.vue +24 -0
  39. package/src/components/ui/Splash.vue +1 -6
  40. package/src/components/ui/StatusBadge.vue +37 -0
  41. package/src/components/ui/controls/CountryChooser.vue +14 -58
  42. package/src/components/ui/controls/atomic/Dropdown.vue +16 -24
  43. package/src/components/ui/controls/atomic/MultiDropdown.vue +7 -1
  44. package/src/components/ui/controls/atomic/Switch.vue +13 -29
  45. package/src/composables/useCopyToClipboard.js +25 -0
  46. package/src/composables/useRowSelection.js +48 -0
  47. package/src/views/Accounts.vue +0 -81
  48. package/src/views/Console.vue +15 -31
  49. package/src/views/Editor.vue +48 -138
  50. package/src/views/FilterBuilder.vue +0 -23
  51. package/src/views/Login.vue +14 -63
  52. package/src/views/Profiles.vue +0 -82
  53. package/src/views/Tasks.vue +205 -231
  54. package/tailwind.config.js +47 -5
@@ -120,27 +120,18 @@ const chose = (f) => {
120
120
 
121
121
  <style scoped>
122
122
  .dropdown {
123
- @apply relative w-full text-white ml-auto rounded-lg ring-0 h-10;
124
- background: linear-gradient(135deg, oklch(0.18 0 0) 0%, oklch(0.20 0 0) 100%);
125
- border: 1px solid oklch(0.26 0 0);
123
+ @apply relative w-full text-white ml-auto rounded-lg ring-0 h-10 bg-dark-300 border border-dark-600;
126
124
  padding: 0.75rem;
127
125
  }
128
126
 
129
127
  .dropdown.transparent {
130
- background: transparent !important;
131
- border: none !important;
132
- box-shadow: none !important;
133
- padding: 0 !important;
134
- height: 40px !important;
128
+ @apply bg-transparent border-0 shadow-none p-0 h-10;
135
129
  }
136
130
 
137
131
  .dropdown.transparent:hover,
138
132
  .dropdown.transparent:focus-within,
139
133
  .dropdown.transparent.opened {
140
- border: none !important;
141
- background: transparent !important;
142
- box-shadow: none !important;
143
- outline: none !important;
134
+ @apply border-0 bg-transparent shadow-none outline-none;
144
135
  }
145
136
 
146
137
  .dropdown:not(.transparent):hover {
@@ -149,14 +140,13 @@ const chose = (f) => {
149
140
 
150
141
  .dropdown:not(.transparent):focus-within,
151
142
  .dropdown:not(.transparent).opened {
152
- border-color: oklch(0.72 0.15 145) !important;
153
- outline: 1px solid oklch(0.72 0.15 145) !important;
143
+ @apply border-border-focus outline outline-1 outline-border-focus;
154
144
  outline-offset: 0;
155
145
  }
156
146
 
157
147
  @media (min-width: 768px) {
158
148
  .dropdown {
159
- height: 40px;
149
+ @apply h-10;
160
150
  padding: 0.625rem;
161
151
  }
162
152
  }
@@ -176,21 +166,23 @@ const chose = (f) => {
176
166
  }
177
167
 
178
168
  .dropdown-arrow {
179
- @apply w-4 h-4 transition-all duration-300 flex-shrink-0;
169
+ @apply w-4 h-4 transition-all duration-300;
170
+ position: absolute;
171
+ right: 0.75rem;
172
+ top: 50%;
173
+ transform: translateY(-50%);
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
180
177
  }
181
178
 
182
179
  .dropdown-menu-portal {
183
- @apply rounded-xl shadow-2xl;
184
- background: linear-gradient(135deg, oklch(0.18 0 0) 0%, oklch(0.20 0 0) 100%);
185
- border: 1px solid oklch(0.26 0 0);
180
+ @apply rounded-xl shadow-2xl bg-dark-300 border border-dark-600 overflow-x-auto overflow-y-auto touch-pan-x touch-pan-y;
186
181
  backdrop-filter: blur(12px);
187
182
  box-shadow: 0 20px 25px -5px oklch(0 0 0 / 0.4), 0 10px 10px -5px oklch(0 0 0 / 0.2),
188
183
  0 0 0 1px oklch(1 0 0 / 0.05);
189
- overflow-x: auto !important;
190
- overflow-y: auto !important;
191
- overscroll-behavior: contain !important;
192
- touch-action: pan-x pan-y !important;
193
- -webkit-overflow-scrolling: touch !important;
184
+ overscroll-behavior: contain;
185
+ -webkit-overflow-scrolling: touch;
194
186
  scrollbar-width: thin;
195
187
  scrollbar-color: oklch(0.35 0 0) oklch(0.19 0 0);
196
188
  z-index: 1000;
@@ -225,7 +225,10 @@ if (props.default && !selectedOptions.value.includes(props.default)) {
225
225
  }
226
226
 
227
227
  .dropdown-counter {
228
- @apply flex items-center gap-2 absolute right-2;
228
+ @apply flex items-center gap-2 absolute;
229
+ right: 0.75rem;
230
+ top: 50%;
231
+ transform: translateY(-50%);
229
232
  }
230
233
 
231
234
  .counter-badge {
@@ -235,6 +238,9 @@ if (props.default && !selectedOptions.value.includes(props.default)) {
235
238
 
236
239
  .dropdown-arrow {
237
240
  @apply min-w-4 min-h-4 transition-all duration-300;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
238
244
  }
239
245
 
240
246
  .dropdown-menu-portal {
@@ -18,39 +18,29 @@ const props = defineProps({
18
18
  <style lang="scss" scoped>
19
19
  /* iOS-style switch */
20
20
  .switch {
21
- position: relative;
22
- display: inline-block;
21
+ @apply relative inline-block;
23
22
  width: 51px;
24
23
  height: 31px;
25
24
  }
26
25
 
27
26
  /* Hide default HTML checkbox */
28
27
  .switch input {
29
- opacity: 0;
30
- width: 0;
31
- height: 0;
28
+ @apply opacity-0 w-0 h-0;
32
29
  }
33
30
 
34
31
  /* The slider */
35
32
  .slider {
36
- position: absolute;
37
- cursor: pointer;
38
- top: 0;
39
- left: 0;
40
- right: 0;
41
- bottom: 0;
33
+ @apply absolute cursor-pointer inset-0;
34
+ @apply border-2;
42
35
  background-color: oklch(0.26 0 0);
43
- border: 2px solid oklch(0.35 0 0);
36
+ border-color: oklch(0.35 0 0);
44
37
  transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
45
38
  }
46
39
 
47
40
  .slider:before {
48
- position: absolute;
41
+ @apply absolute;
42
+ @apply w-[23px] h-[23px] left-0.5 bottom-0.5;
49
43
  content: "";
50
- height: 23px;
51
- width: 23px;
52
- left: 2px;
53
- bottom: 2px;
54
44
  background-color: oklch(0.50 0 0);
55
45
  transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
56
46
  box-shadow: 0 1px 3px oklch(0 0 0 / 0.3);
@@ -70,16 +60,15 @@ input:checked + .slider:before {
70
60
  }
71
61
 
72
62
  .switch.disabled {
73
- pointer-events: none;
63
+ @apply pointer-events-none;
74
64
  }
75
65
 
76
66
  input:disabled + .slider {
77
- opacity: 0.5;
78
- cursor: not-allowed;
67
+ @apply opacity-50 cursor-not-allowed;
79
68
  }
80
69
 
81
70
  input:disabled + .slider:before {
82
- opacity: 0.7;
71
+ @apply opacity-70;
83
72
  }
84
73
 
85
74
  /* Rounded sliders */
@@ -93,20 +82,15 @@ input:disabled + .slider:before {
93
82
 
94
83
  @media (max-width: 810px) {
95
84
  .switch {
96
- width: 44px;
97
- min-width: 44px;
98
- height: 26px;
85
+ @apply w-11 min-w-11 h-[26px];
99
86
 
100
87
  .slider:before {
101
- width: 22px;
102
- height: 22px;
103
- left: 2px;
104
- bottom: 2px;
88
+ @apply w-[22px] h-[22px] left-0.5 bottom-0.5;
105
89
  }
106
90
  }
107
91
 
108
92
  input:checked + .slider:before {
109
- transform: translateX(18px);
93
+ @apply translate-x-[18px];
110
94
  }
111
95
  }
112
96
  </style>
@@ -0,0 +1,25 @@
1
+ import { useUIStore } from '@/stores/ui'
2
+
3
+ /**
4
+ * useCopyToClipboard - Composable for copying text with user feedback
5
+ *
6
+ * @returns {Object} Copy function with success notification
7
+ */
8
+ export function useCopyToClipboard() {
9
+ const ui = useUIStore()
10
+
11
+ const copy = (text, message = 'Copied to clipboard') => {
12
+ if (!text) return
13
+
14
+ navigator.clipboard.writeText(text)
15
+ .then(() => {
16
+ ui.showSuccess(message)
17
+ })
18
+ .catch((err) => {
19
+ ui.showError('Failed to copy')
20
+ console.error('Copy failed:', err)
21
+ })
22
+ }
23
+
24
+ return { copy }
25
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * useRowSelection - Composable for handling row selection via double-click/tap
3
+ *
4
+ * Provides consistent double-click and double-tap behavior for table rows.
5
+ * Prevents selection when clicking on buttons or checkboxes.
6
+ *
7
+ * @param {Function} toggleCallback - Function to call when row is double-clicked/tapped
8
+ * @returns {Object} Event handlers for @dblclick, @touchstart, @touchend
9
+ */
10
+ export function useRowSelection(toggleCallback) {
11
+ let lastTapTime = 0
12
+ const DOUBLE_TAP_DELAY = 300
13
+
14
+ const handleDoubleClick = (event) => {
15
+ // Don't trigger on button or checkbox clicks
16
+ if (event.target.closest('button') || event.target.closest('.checkbox')) {
17
+ return
18
+ }
19
+ toggleCallback()
20
+ }
21
+
22
+ const handleTouchStart = (event) => {
23
+ const currentTime = Date.now()
24
+ const tapGap = currentTime - lastTapTime
25
+
26
+ if (tapGap < DOUBLE_TAP_DELAY && tapGap > 0) {
27
+ // Don't trigger on button or checkbox taps
28
+ if (!event.target.closest('button') && !event.target.closest('.checkbox')) {
29
+ event.preventDefault()
30
+ toggleCallback()
31
+ }
32
+ }
33
+ lastTapTime = currentTime
34
+ }
35
+
36
+ const handleTouchEnd = (event) => {
37
+ // Prevent default touch behavior on buttons/checkboxes
38
+ if (event.target.closest('button') || event.target.closest('.checkbox')) {
39
+ return
40
+ }
41
+ }
42
+
43
+ return {
44
+ handleDoubleClick,
45
+ handleTouchStart,
46
+ handleTouchEnd
47
+ }
48
+ }
@@ -94,12 +94,6 @@
94
94
  </div>
95
95
  </template>
96
96
  <style lang="scss" scoped>
97
- .custom-dropdown-content {
98
- top: 2.6rem !important;
99
- left: -13px;
100
- @apply border border-dark-650;
101
- }
102
-
103
97
  /* Page buttons styling - match Tasks page */
104
98
  button.bg-dark-400 {
105
99
  &:active,
@@ -110,81 +104,6 @@ button.bg-dark-400 {
110
104
  }
111
105
  }
112
106
 
113
- /* Unified search component group */
114
- .unified-search-group {
115
- border: 2px solid oklch(0.2809 0 0);
116
- border-radius: 0.5rem;
117
- overflow: hidden;
118
- background: oklch(0.2603 0 0);
119
- transition: all 0.15s ease;
120
- display: flex;
121
-
122
- :deep(.tag-toggle),
123
- :deep(.dropdown),
124
- input {
125
- border: none !important;
126
- border-width: 0 !important;
127
- border-radius: 0 !important;
128
- height: 40px !important;
129
- background: transparent !important;
130
- box-shadow: none !important;
131
- min-width: fit-content !important;
132
- }
133
-
134
- :deep(.tag-toggle) {
135
- padding: 0;
136
- border: 0 solid transparent !important;
137
- border-top: 0 !important;
138
- border-bottom: 0 !important;
139
- border-left: 0 !important;
140
- border-right: 0 !important;
141
- }
142
-
143
- :deep(.tag-toggle button) {
144
- font-size: 0.875rem;
145
- padding: 0.5rem 0.75rem;
146
- min-width: fit-content;
147
- border: 0 solid transparent !important;
148
- border-top: 0 !important;
149
- border-bottom: 0 !important;
150
- border-left: 0 !important;
151
- border-right: 0 !important;
152
- }
153
-
154
- :deep(.dropdown) {
155
- width: 120px !important;
156
- min-width: 120px !important;
157
- }
158
-
159
- :deep(.dropdown-display) {
160
- padding: 0 0.75rem;
161
- font-size: 0.875rem;
162
- }
163
-
164
- input {
165
- padding-left: 0.75rem;
166
- padding-right: 0.75rem;
167
- flex: 1;
168
-
169
- &::placeholder {
170
- color: oklch(0.50 0 0);
171
- }
172
-
173
- &:focus {
174
- outline: none !important;
175
- }
176
- }
177
-
178
- &:hover {
179
- border-color: oklch(0.30 0 0);
180
- }
181
-
182
- &:focus-within {
183
- border-color: oklch(0.72 0.15 145);
184
- outline: 1px solid oklch(0.72 0.15 145);
185
- outline-offset: 0;
186
- }
187
- }
188
107
  .max-h-big-acc {
189
108
  max-height: calc(100vh - 14rem);
190
109
  overflow: auto;
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div>
9
9
  <div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
10
- <div class="flex flex-col gap-3 md:flex-row md:items-center md:flex-1">
10
+ <div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
11
11
  <div class="w-full md:w-64">
12
12
  <Dropdown
13
13
  class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
@@ -22,8 +22,8 @@
22
22
  .sort((a, b) => a.localeCompare(b))
23
23
  " />
24
24
  </div>
25
- <div class="flex items-center gap-2 flex-1">
26
- <div class="input-default flex items-center flex-1 md:max-w-64">
25
+ <div class="flex flex-1 items-center gap-2">
26
+ <div class="input-default flex flex-1 items-center md:max-w-64">
27
27
  <input
28
28
  v-model="searchQuery"
29
29
  type="text"
@@ -52,7 +52,7 @@
52
52
  </button>
53
53
  </div>
54
54
  </div>
55
- <div class="flex items-center gap-3 hidden md:flex">
55
+ <div class="flex hidden items-center gap-3 md:flex">
56
56
  <!-- Hide Monitors and Auto buttons only on desktop -->
57
57
  <div class="hidden items-center gap-3 md:flex">
58
58
  <button
@@ -72,7 +72,7 @@
72
72
  </div>
73
73
  <!-- Scroll buttons - desktop only (mobile has them inline with search) -->
74
74
  <button
75
- class="hidden md:flex h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200"
75
+ class="hidden h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
76
76
  @mousedown="startScrolling('up')"
77
77
  @mouseup="stopScrolling"
78
78
  @mouseleave="stopScrolling"
@@ -81,7 +81,7 @@
81
81
  <UpIcon class="pointer-events-none h-5 w-5" />
82
82
  </button>
83
83
  <button
84
- class="hidden md:flex h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200"
84
+ class="hidden h-10 w-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
85
85
  @mousedown="startScrolling('down')"
86
86
  @mouseup="stopScrolling"
87
87
  @mouseleave="stopScrolling"
@@ -94,7 +94,7 @@
94
94
 
95
95
  <Smoothie
96
96
  :weight="0.2"
97
- class="console scrollable smooth-scroll overflow-y-auto overflow-x-auto font-mono text-white"
97
+ class="console scrollable smooth-scroll overflow-x-auto overflow-y-auto font-mono text-white"
98
98
  style="min-height: 12rem !important"
99
99
  ref="$autoscroll"
100
100
  @wheel.stop
@@ -118,7 +118,7 @@
118
118
  v-bind:key="`log-${index}`"
119
119
  :style="{ '--index': index }"><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
120
120
  </Smoothie>
121
- <div class="console-switches mt-4 mb-6 flex justify-between md:hidden">
121
+ <div class="console-switches mb-6 mt-4 flex justify-between md:hidden">
122
122
  <button
123
123
  class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
124
124
  <h3 class="text-sm text-white">Hide Monitors</h3>
@@ -139,31 +139,25 @@
139
139
  </template>
140
140
  <style lang="scss" scoped>
141
141
  .console-page {
142
- // Add generous bottom padding on mobile to prevent switch cutoff
143
- @media (max-width: 768px) {
144
- padding-bottom: 4rem;
145
- margin-bottom: 2rem;
146
- }
142
+ @apply pb-16 mb-8 md:pb-0 md:mb-0;
147
143
 
148
144
  @media (max-width: 480px) and (orientation: portrait) {
149
- padding-bottom: 6rem;
150
- margin-bottom: 3rem;
145
+ @apply pb-24 mb-12;
151
146
  }
152
147
  }
153
148
 
154
149
  .console-switches {
155
150
  @media (max-width: 480px) and (orientation: portrait) {
156
- margin-top: 1.5rem !important;
157
- margin-bottom: 4rem !important;
151
+ @apply mt-6 mb-16;
158
152
  }
159
153
  }
160
154
 
161
155
  .console {
162
156
  @apply relative rounded border-2 border-dark-550 bg-dark-400 p-2 lg:p-5;
157
+ @apply touch-pan-x touch-pan-y;
163
158
  height: calc(100vh - 18rem);
164
159
  scrollbar-width: thin;
165
160
  scrollbar-color: oklch(0.35 0 0) oklch(0.19 0 0);
166
- touch-action: pan-x pan-y !important;
167
161
  -webkit-overflow-scrolling: touch;
168
162
 
169
163
  // Use fixed height on mobile portrait to ensure switches are visible
@@ -261,7 +255,8 @@
261
255
  border-color: oklch(0.2809 0 0);
262
256
  transition: all 0.15s ease;
263
257
 
264
- &:hover, &:active {
258
+ &:hover,
259
+ &:active {
265
260
  border-color: oklch(0.72 0.15 145) !important;
266
261
  outline: 1px solid oklch(0.72 0.15 145);
267
262
  outline-offset: 0;
@@ -292,17 +287,6 @@
292
287
  }
293
288
  }
294
289
 
295
- .text-xxs {
296
- font-size: 0.5rem;
297
- line-height: 0.8rem;
298
- }
299
-
300
- .header-wrapper {
301
- svg {
302
- width: 30px;
303
- height: 30px;
304
- }
305
- }
306
290
  /* Console-specific styles handled by utilities */
307
291
  </style>
308
292
  <script setup>
@@ -525,7 +509,7 @@ window.startDebugConsoleMessages = () => {
525
509
  setInterval(() => {
526
510
  var taskId = Math.round(Math.random() * 10);
527
511
  const log = {
528
- log: `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${taskId}]\u001b[00m TEST TEST TEST TEST TEST TEST TEST`,
512
+ log: `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${taskId}]\u001b[00m TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST`,
529
513
  metadata: {
530
514
  taskId: taskId,
531
515
  siteId: "TM_US",