@necrolab/dashboard 0.5.12 → 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 +2 -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 +4 -21
  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 +3 -24
  54. package/tailwind.config.js +47 -5
@@ -12,6 +12,7 @@
12
12
  @use "components/toasts";
13
13
  @use "components/modals";
14
14
  @use "components/tables";
15
+ @use "components/search-groups";
15
16
 
16
17
  /* ==========================================================================
17
18
  BODY LAYOUT & BACKGROUND
@@ -51,10 +52,6 @@ body {
51
52
  }
52
53
  }
53
54
 
54
- img {
55
- pointer-events: none;
56
- }
57
-
58
55
  /* Global icon color consistency */
59
56
  svg {
60
57
  color: oklch(0.9 0 0) !important;
@@ -199,19 +196,6 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
199
196
  }
200
197
  }
201
198
 
202
- /* ==========================================================================
203
- SCROLLBAR UTILITIES
204
- ========================================================================== */
205
-
206
- .hidden-scrollbars {
207
- scrollbar-width: none;
208
- -ms-overflow-style: none;
209
-
210
- &::-webkit-scrollbar {
211
- display: none;
212
- }
213
- }
214
-
215
199
  /* ==========================================================================
216
200
  DYNAMIC HEIGHTS & RESPONSIVE DESIGN
217
201
  ========================================================================== */
@@ -270,20 +254,10 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
270
254
  }
271
255
 
272
256
  /* ==========================================================================
273
- MOBILE RESPONSIVE OPTIMIZATIONS
257
+ STATUS INDICATOR RESPONSIVE SIZES
274
258
  ========================================================================== */
275
259
 
276
260
  @screen md {
277
- .btn-primary,
278
- .btn-secondary {
279
- @apply px-3 py-1.5 text-sm;
280
- }
281
-
282
- .btn-action {
283
- @apply h-12 px-6 text-sm;
284
- min-height: 48px;
285
- }
286
-
287
261
  .status-indicator {
288
262
  width: 6px;
289
263
  height: 6px;
@@ -293,13 +267,6 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
293
267
  }
294
268
 
295
269
  @screen mobile-portrait {
296
- .btn-action {
297
- @apply h-14 px-8 text-base;
298
- min-height: 56px;
299
- font-weight: 600;
300
- border-radius: 8px;
301
- }
302
-
303
270
  .status-indicator {
304
271
  width: 5px;
305
272
  height: 5px;
@@ -307,11 +274,3 @@ svg:not([stroke]):not([fill="none"]) polygon:not([fill]) {
307
274
  min-height: 5px;
308
275
  }
309
276
  }
310
-
311
- @screen mobile-landscape {
312
- .btn-action {
313
- @apply h-14 px-8 text-base;
314
- min-height: 56px;
315
- font-weight: 600;
316
- }
317
- }
@@ -11,22 +11,17 @@
11
11
  class="ml-0 mr-4"
12
12
  :toggled="props.account.selected"
13
13
  @valueUpdate="ui.toggleAccountSelected(props.account.id)" />
14
- <h4 class="mx-auto text-white" @click="copy(props.account.email)">
14
+ <h4 class="mx-auto text-white" @click="copy(props.account.email, 'Copied email')">
15
15
  {{ props.account.email }}
16
16
  </h4>
17
17
  </div>
18
- <div class="col-span-2 hidden md:block" @click="copy(props.account.password)">
18
+ <div class="col-span-2 hidden md:block" @click="copy(props.account.password, 'Copied password')">
19
19
  <h4 class="text-white">
20
20
  {{ props.account.privacy ? "•".repeat(props.account.password.length) : props.account.password }}
21
21
  </h4>
22
22
  </div>
23
- <div class="col-span-1">
24
- <div v-if="props.account.enabled" class="enabled-badge">
25
- <CheckmarkIcon class="w-3.5 h-3.5" />
26
- </div>
27
- <div v-else class="disabled-badge">
28
- <CloseXIcon class="w-3.5 h-3.5" />
29
- </div>
23
+ <div class="col-span-1 flex justify-center">
24
+ <StatusBadge :enabled="props.account.enabled" size="small" />
30
25
  </div>
31
26
 
32
27
  <div class="col-span-1 hidden lg:block">
@@ -36,7 +31,7 @@
36
31
  </div>
37
32
 
38
33
  <div class="col-span-1 flex">
39
- <ul class="account-buttons">
34
+ <ActionButtonGroup>
40
35
  <li>
41
36
  <button @click="edit">
42
37
  <EditIcon />
@@ -52,203 +47,33 @@
52
47
  <img class="h-4 w-4" src="/img/controls/enable.svg" />
53
48
  </button>
54
49
  </li>
55
- </ul>
50
+ </ActionButtonGroup>
56
51
  </div>
57
52
  </Row>
58
53
  </template>
59
54
  <style lang="scss" scoped>
60
- .enabled-badge {
61
- @apply flex items-center justify-center mx-auto rounded-full;
62
- background: oklch(0.72 0.15 145 / 0.12);
63
- border: 1.5px solid oklch(0.72 0.15 145);
64
- width: 24px;
65
- height: 24px;
66
- padding: 0;
67
-
68
- svg {
69
- color: oklch(0.72 0.15 145) !important;
70
- width: 12px !important;
71
- height: auto !important;
72
- max-height: 12px !important;
73
- display: block;
74
- margin: 0;
75
- }
76
-
77
- svg path {
78
- stroke: oklch(0.72 0.15 145) !important;
79
- fill: oklch(0.72 0.15 145) !important;
80
- }
81
- }
82
-
83
- .disabled-badge {
84
- @apply flex items-center justify-center mx-auto rounded-full;
85
- background: oklch(0.60 0.20 25 / 0.12);
86
- border: 1.5px solid oklch(0.60 0.20 25);
87
- width: 24px;
88
- height: 24px;
89
- padding: 0;
90
- color: oklch(0.60 0.20 25) !important;
91
-
92
- svg {
93
- color: oklch(0.60 0.20 25) !important;
94
- width: 12px !important;
95
- height: 12px !important;
96
- display: block;
97
- margin: 0;
98
- fill: oklch(0.60 0.20 25) !important;
99
- }
100
-
101
- svg path {
102
- stroke: oklch(0.60 0.20 25) !important;
103
- fill: oklch(0.60 0.20 25) !important;
104
- }
105
- }
106
-
107
55
  h4 {
108
56
  @apply text-center;
109
57
  }
110
- .account-buttons {
111
- @apply mx-auto flex items-center justify-center rounded;
112
- background: oklch(0.2046 0 0);
113
- border: 2px solid oklch(0.2809 0 0);
114
- padding: 3px;
115
- gap: 2px;
116
- flex-shrink: 0;
117
- overflow: visible;
118
-
119
- button {
120
- @apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
121
- background: transparent;
122
- width: 28px;
123
- height: 28px;
124
- color: oklch(0.90 0 0);
125
- border-radius: 6px;
126
-
127
- &:hover {
128
- background: oklch(0.72 0.15 145 / 0.15);
129
- color: oklch(1 0 0);
130
- }
131
-
132
- &:active {
133
- background: oklch(0.72 0.15 145 / 0.25);
134
- }
135
- }
136
-
137
- svg,
138
- img {
139
- width: 16px;
140
- height: 16px;
141
- position: relative;
142
- z-index: 1;
143
- }
144
-
145
- svg path {
146
- fill: currentColor;
147
- }
148
- }
149
-
150
- // Tablet sizing - medium buttons
151
- @media (min-width: 768px) and (max-width: 1023px) {
152
- .account-buttons {
153
- padding: 2px;
154
- gap: 1px;
155
- border-radius: 6px;
156
-
157
- button {
158
- width: 26px;
159
- height: 26px;
160
- border-radius: 5px;
161
- }
162
-
163
- svg,
164
- img {
165
- width: 14px;
166
- height: 14px;
167
- }
168
- }
169
- }
170
-
171
- // Desktop sizing - large buttons
172
- @media (min-width: 1024px) {
173
- .account-buttons {
174
- padding: 3px;
175
- gap: 2px;
176
- border-radius: 8px;
177
-
178
- button {
179
- width: 28px;
180
- height: 28px;
181
- border-radius: 6px;
182
- }
183
-
184
- svg,
185
- img {
186
- width: 16px;
187
- height: 16px;
188
- }
189
- }
190
- }
191
-
192
- // Mobile specific styling
193
- @media (max-width: 640px) {
194
- .account-buttons {
195
- padding: 1px;
196
- gap: 1px;
197
- border-radius: 4px;
198
- border: 2px solid oklch(0.2809 0 0) !important;
199
- max-width: 100%;
200
- min-height: 28px;
201
- height: auto;
202
- flex-wrap: wrap;
203
- justify-content: center;
204
- align-items: center;
205
- background: oklch(0.2046 0 0);
206
-
207
- button {
208
- width: 20px;
209
- height: 20px;
210
- border-radius: 3px;
211
- min-width: 20px;
212
- border: none !important;
213
- flex-shrink: 0;
214
- margin: 0.5px;
215
- background: transparent;
216
-
217
- &:hover {
218
- background: oklch(0.72 0.15 145 / 0.15);
219
- }
220
-
221
- &:active {
222
- background: oklch(0.72 0.15 145 / 0.25);
223
- }
224
- }
225
-
226
- svg,
227
- img {
228
- width: 12px;
229
- height: 12px;
230
- }
231
- }
232
- }
233
58
  </style>
234
59
  <script setup>
235
60
  import { Row } from "@/components/Table";
236
- import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon, CheckmarkIcon, CloseXIcon } from "@/components/icons";
61
+ import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
237
62
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
63
+ import StatusBadge from "@/components/ui/StatusBadge.vue";
64
+ import ActionButtonGroup from "@/components/ui/ActionButtonGroup.vue";
238
65
  import { useUIStore } from "@/stores/ui";
239
66
  import TagLabel from "@/components/Editors/TagLabel.vue";
67
+ import { useRowSelection } from "@/composables/useRowSelection";
68
+ import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
240
69
 
241
70
  const ui = useUIStore();
71
+ const { copy } = useCopyToClipboard();
242
72
 
243
73
  const props = defineProps({
244
74
  account: { type: Object }
245
75
  });
246
76
 
247
- const copy = (txt) => {
248
- if (!txt) return;
249
- navigator.clipboard.writeText(txt);
250
- ui.showSuccess("Copied text");
251
- };
252
77
  const enable = async () => await ui.addAccount({ ...props.account, enabled: true });
253
78
  const disable = async () => await ui.addAccount({ ...props.account, enabled: false });
254
79
  const edit = () => {
@@ -257,38 +82,7 @@ const edit = () => {
257
82
  };
258
83
 
259
84
  // Double-click/tap selection
260
- let lastTapTime = 0;
261
- const DOUBLE_TAP_DELAY = 300; // ms
262
-
263
- const handleDoubleClick = (event) => {
264
- // Prevent if clicking on buttons or checkbox
265
- if (event.target.closest('button') || event.target.closest('.checkbox')) {
266
- return;
267
- }
85
+ const { handleDoubleClick, handleTouchStart, handleTouchEnd } = useRowSelection(() => {
268
86
  ui.toggleAccountSelected(props.account.id);
269
- };
270
-
271
- const handleTouchStart = (event) => {
272
- // Store touch time for double-tap detection
273
- const currentTime = Date.now();
274
- const tapGap = currentTime - lastTapTime;
275
-
276
- if (tapGap < DOUBLE_TAP_DELAY && tapGap > 0) {
277
- // Double-tap detected
278
- if (!event.target.closest('button') && !event.target.closest('.checkbox')) {
279
- event.preventDefault(); // Prevent zoom
280
- ui.toggleAccountSelected(props.account.id);
281
- }
282
- }
283
-
284
- lastTapTime = currentTime;
285
- };
286
-
287
- const handleTouchEnd = (event) => {
288
- // Prevent default to avoid potential zoom issues
289
- // but only if not interacting with buttons/checkbox
290
- if (event.target.closest('button') || event.target.closest('.checkbox')) {
291
- return;
292
- }
293
- };
87
+ });
294
88
  </script>
@@ -127,10 +127,6 @@
127
127
  border-color: oklch(0.28 0 0);
128
128
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
129
129
  }
130
-
131
- .z-inf {
132
- z-index: 99999999999999;
133
- }
134
130
  </style>
135
131
  <script setup>
136
132
  import Modal from "@/components/ui/Modal.vue";
@@ -66,7 +66,6 @@ h4 {
66
66
  }
67
67
 
68
68
  .empty-state {
69
- color: #969696;
70
69
  font-size: 14px;
71
70
  font-weight: 500;
72
71
  }
@@ -9,7 +9,7 @@
9
9
  <div>
10
10
  <div class="my-3 grid grid-cols-12 gap-3">
11
11
  <!-- Account tag -->
12
- <div class="input-wrapper relative-positioned z-tooltip col-span-4">
12
+ <div class="input-wrapper col-span-4">
13
13
  <label class="label-override mb-2">
14
14
  Account Tag
15
15
  <TagIcon />
@@ -25,44 +25,32 @@
25
25
  </div>
26
26
 
27
27
  <!-- Email -->
28
- <div class="input-wrapper z-0 col-span-8">
29
- <label class="label-override mb-2">
30
- Email
31
- <MailIcon />
32
- </label>
33
- <div :class="`input-default required ${errors.includes('email') ? 'error' : ''}`">
34
- <input
35
- placeholder="email@example.com"
36
- type="email"
37
- v-model="account.email"
38
- required
39
- autocomplete="new-password"
40
- name="not-email-field-random-12345"
41
- data-dashlane-rid=""
42
- data-dashlane-label=""
43
- data-dashlane-classification=""
44
- data-form-type="other"
45
- role="textbox"
46
- inputmode="email" />
47
- </div>
48
- </div>
28
+ <FormField label="Email" :icon="MailIcon" required :error="errors.includes('email')" z-index="0" class="col-span-8">
29
+ <input
30
+ placeholder="email@example.com"
31
+ type="email"
32
+ v-model="account.email"
33
+ required
34
+ autocomplete="new-password"
35
+ name="not-email-field-random-12345"
36
+ data-dashlane-rid=""
37
+ data-dashlane-label=""
38
+ data-dashlane-classification=""
39
+ data-form-type="other"
40
+ role="textbox"
41
+ inputmode="email" />
42
+ </FormField>
49
43
 
50
44
  <!-- Password -->
51
- <div class="input-wrapper z-0 col-span-12">
52
- <label class="label-override mb-2">
53
- Password
54
- <KeyIcon />
55
- </label>
56
- <div :class="`input-default required ${errors.includes('password') ? 'error' : ''}`">
57
- <input
58
- placeholder="***********"
59
- type="password"
60
- v-model="account.password"
61
- required
62
- autocomplete="off"
63
- name="account_password_disableautocomplete" />
64
- </div>
65
- </div>
45
+ <FormField label="Password" :icon="KeyIcon" required :error="errors.includes('password')" z-index="0" class="col-span-12">
46
+ <input
47
+ placeholder="***********"
48
+ type="password"
49
+ v-model="account.password"
50
+ required
51
+ autocomplete="off"
52
+ name="account_password_disableautocomplete" />
53
+ </FormField>
66
54
  </div>
67
55
 
68
56
  <!-- Readonly fields when editing -->
@@ -76,12 +64,7 @@
76
64
  <div class="col-span-6">
77
65
  <label class="label-override mb-2">Status</label>
78
66
  <div class="flex items-center gap-3 h-10">
79
- <div v-if="ui.currentlyEditing.enabled" class="enabled-badge-large">
80
- <CheckmarkIcon />
81
- </div>
82
- <div v-else class="disabled-badge-large">
83
- <CloseXIcon />
84
- </div>
67
+ <StatusBadge :enabled="ui.currentlyEditing.enabled" size="large" />
85
68
  <span class="text-sm font-medium" :class="ui.currentlyEditing.enabled ? 'text-green-400' : 'text-red-400'">
86
69
  {{ ui.currentlyEditing.enabled ? 'Enabled' : 'Disabled' }}
87
70
  </span>
@@ -99,75 +82,22 @@
99
82
  </Modal>
100
83
  </template>
101
84
  <style lang="scss" scoped>
102
- .enabled-badge-large {
103
- @apply flex items-center justify-center rounded-full;
104
- background: oklch(0.72 0.15 145 / 0.12);
105
- border: 1.5px solid oklch(0.72 0.15 145);
106
- width: 26px;
107
- height: 26px;
108
- padding: 0;
109
-
110
- svg {
111
- color: oklch(0.72 0.15 145) !important;
112
- width: 14px !important;
113
- height: auto !important;
114
- max-height: 14px !important;
115
- display: block;
116
- margin: 0;
117
- }
118
-
119
- svg path {
120
- stroke: oklch(0.72 0.15 145) !important;
121
- fill: oklch(0.72 0.15 145) !important;
122
- }
123
- }
124
-
125
- .disabled-badge-large {
126
- @apply flex items-center justify-center rounded-full;
127
- background: oklch(0.60 0.20 25 / 0.12);
128
- border: 1.5px solid oklch(0.60 0.20 25);
129
- width: 26px;
130
- height: 26px;
131
- padding: 0;
132
- color: oklch(0.60 0.20 25) !important;
85
+ .label-override {
86
+ @apply flex items-center;
87
+ color: #e1e1e4 !important;
133
88
 
134
89
  svg {
135
- color: oklch(0.60 0.20 25) !important;
136
- width: 14px !important;
137
- height: 14px !important;
138
- display: block;
139
- margin: 0;
140
- fill: oklch(0.60 0.20 25) !important;
141
- }
142
-
143
- svg path {
144
- stroke: oklch(0.60 0.20 25) !important;
145
- fill: oklch(0.60 0.20 25) !important;
146
- }
147
- }
90
+ @apply ml-2;
148
91
 
149
- .input-wrapper {
150
- label {
151
- @apply flex;
92
+ path {
93
+ fill: #e1e1e4 !important;
94
+ }
152
95
  }
153
96
  }
154
- .z-0 {
155
- z-index: 0 !important;
156
- }
157
- .z-1 {
158
- z-index: 1 !important;
159
- }
160
- .z-2 {
161
- z-index: 2 !important;
162
- }
163
-
164
- .error {
165
- border-width: 2px !important;
166
- border-color: rgb(238 130 130) !important;
167
- }
168
97
  </style>
169
98
  <script setup>
170
99
  import Modal from "@/components/ui/Modal.vue";
100
+ import FormField from "@/components/ui/FormField.vue";
171
101
  import {
172
102
  EditIcon,
173
103
  MailIcon,
@@ -176,12 +106,11 @@ import {
176
106
  TimerIcon,
177
107
  SandclockIcon,
178
108
  TagIcon,
179
- ScannerIcon,
180
- CheckmarkIcon,
181
- CloseXIcon
109
+ ScannerIcon
182
110
  } from "@/components/icons";
183
111
  import { useUIStore } from "@/stores/ui";
184
112
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
113
+ import StatusBadge from "@/components/ui/StatusBadge.vue";
185
114
  import TagLabel from "@/components/Editors/TagLabel.vue";
186
115
 
187
116
  import { ref } from "vue";