@necrolab/dashboard 0.4.220 → 0.5.1

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 (140) hide show
  1. package/.prettierrc +27 -1
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +64 -2
  4. package/artwork/image.png +0 -0
  5. package/backend/api.js +26 -24
  6. package/backend/auth.js +2 -2
  7. package/backend/batching.js +1 -1
  8. package/backend/endpoints.js +8 -11
  9. package/backend/index.js +2 -2
  10. package/backend/mock-data.js +27 -36
  11. package/backend/mock-src/classes/logger.js +5 -7
  12. package/backend/mock-src/classes/utils.js +3 -2
  13. package/backend/mock-src/ticketmaster.js +4 -4
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +0 -1
  16. package/dev-server.js +134 -0
  17. package/exit +209 -0
  18. package/index.html +78 -8
  19. package/index.js +1 -1
  20. package/jsconfig.json +16 -0
  21. package/package.json +39 -25
  22. package/postcss.config.js +1 -1
  23. package/postinstall.js +124 -20
  24. package/public/android-chrome-192x192.png +0 -0
  25. package/public/android-chrome-512x512.png +0 -0
  26. package/public/apple-touch-icon.png +0 -0
  27. package/public/favicon-16x16.png +0 -0
  28. package/public/favicon-32x32.png +0 -0
  29. package/public/favicon.ico +0 -0
  30. package/public/img/logo_trans.png +0 -0
  31. package/public/img/necro_logo.png +0 -0
  32. package/public/manifest.json +16 -10
  33. package/run +176 -9
  34. package/src/App.vue +498 -85
  35. package/src/assets/css/base/reset.scss +43 -0
  36. package/src/assets/css/base/scroll.scss +114 -0
  37. package/src/assets/css/base/typography.scss +37 -0
  38. package/src/assets/css/components/buttons.scss +216 -0
  39. package/src/assets/css/components/forms.scss +221 -0
  40. package/src/assets/css/components/modals.scss +13 -0
  41. package/src/assets/css/components/tables.scss +27 -0
  42. package/src/assets/css/components/toasts.scss +100 -0
  43. package/src/assets/css/main.scss +201 -122
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/background.svg.backup +11 -0
  46. package/src/assets/img/logo_trans.png +0 -0
  47. package/src/components/Auth/LoginForm.vue +62 -11
  48. package/src/components/Editors/Account/Account.vue +116 -40
  49. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  50. package/src/components/Editors/Account/AccountView.vue +102 -34
  51. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  52. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  53. package/src/components/Editors/Profile/Profile.vue +132 -47
  54. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  55. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  56. package/src/components/Editors/TagLabel.vue +67 -6
  57. package/src/components/Editors/TagToggle.vue +7 -2
  58. package/src/components/Filter/Filter.vue +288 -71
  59. package/src/components/Filter/FilterPreview.vue +202 -31
  60. package/src/components/Filter/PriceSortToggle.vue +76 -6
  61. package/src/components/Table/Header.vue +1 -1
  62. package/src/components/Table/Row.vue +1 -1
  63. package/src/components/Table/Table.vue +19 -2
  64. package/src/components/Tasks/CheckStock.vue +6 -8
  65. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  66. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  67. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  68. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  69. package/src/components/Tasks/MassEdit.vue +4 -6
  70. package/src/components/Tasks/QuickSettings.vue +199 -30
  71. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  72. package/src/components/Tasks/Stats.vue +50 -24
  73. package/src/components/Tasks/Task.vue +384 -179
  74. package/src/components/Tasks/TaskLabel.vue +2 -2
  75. package/src/components/Tasks/TaskView.vue +136 -48
  76. package/src/components/Tasks/Utilities.vue +25 -10
  77. package/src/components/Tasks/ViewTask.vue +321 -0
  78. package/src/components/icons/Bag.vue +1 -1
  79. package/src/components/icons/Check.vue +5 -0
  80. package/src/components/icons/Close.vue +21 -0
  81. package/src/components/icons/CloseX.vue +5 -0
  82. package/src/components/icons/Eye.vue +6 -0
  83. package/src/components/icons/Key.vue +21 -0
  84. package/src/components/icons/Loyalty.vue +1 -1
  85. package/src/components/icons/Mail.vue +2 -2
  86. package/src/components/icons/Pencil.vue +21 -0
  87. package/src/components/icons/Play.vue +2 -2
  88. package/src/components/icons/Profile.vue +18 -0
  89. package/src/components/icons/Reload.vue +4 -5
  90. package/src/components/icons/Sandclock.vue +2 -2
  91. package/src/components/icons/Sell.vue +21 -0
  92. package/src/components/icons/Spinner.vue +42 -0
  93. package/src/components/icons/SquareCheck.vue +18 -0
  94. package/src/components/icons/SquareUncheck.vue +18 -0
  95. package/src/components/icons/Stadium.vue +1 -1
  96. package/src/components/icons/Wildcard.vue +18 -0
  97. package/src/components/icons/index.js +26 -1
  98. package/src/components/ui/Modal.vue +107 -13
  99. package/src/components/ui/Navbar.vue +175 -40
  100. package/src/components/ui/ReconnectIndicator.vue +351 -55
  101. package/src/components/ui/Splash.vue +5 -35
  102. package/src/components/ui/controls/CountryChooser.vue +200 -62
  103. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  104. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  105. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  106. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  107. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  108. package/src/composables/useClickOutside.js +21 -0
  109. package/src/composables/useDropdownPosition.js +174 -0
  110. package/src/libs/Filter.js +60 -24
  111. package/src/registerServiceWorker.js +1 -1
  112. package/src/stores/connection.js +4 -4
  113. package/src/stores/sampleData.js +172 -199
  114. package/src/stores/ui.js +55 -20
  115. package/src/stores/utils.js +30 -4
  116. package/src/types/index.js +41 -0
  117. package/src/utils/debug.js +1 -0
  118. package/src/views/Accounts.vue +116 -50
  119. package/src/views/Console.vue +394 -79
  120. package/src/views/Editor.vue +1176 -123
  121. package/src/views/FilterBuilder.vue +528 -250
  122. package/src/views/Login.vue +76 -14
  123. package/src/views/Profiles.vue +119 -34
  124. package/src/views/Tasks.vue +266 -98
  125. package/static/offline.html +192 -50
  126. package/switch-branch.sh +41 -0
  127. package/tailwind.config.js +119 -27
  128. package/vite.config.js +73 -16
  129. package/workbox-config.cjs +63 -0
  130. package/ICONS.md +0 -21
  131. package/public/img/background.svg +0 -14
  132. package/public/img/logo.png +0 -0
  133. package/public/img/logo_icon.png +0 -0
  134. package/public/img/logo_icon_2.png +0 -0
  135. package/src/assets/css/_input.scss +0 -143
  136. package/src/assets/img/logo.png +0 -0
  137. package/src/assets/img/logo_icon.png +0 -0
  138. package/src/assets/img/logo_icon_2.png +0 -0
  139. package/vue.config.js +0 -32
  140. package/workbox-config.js +0 -7
@@ -1,54 +1,52 @@
1
1
  <template>
2
2
  <Row
3
- class="relative text-white grid grid-cols-5 ipadlg:grid-cols-7 grid-template-colums h-16"
3
+ class="relative h-16 grid-cols-5 text-white md:grid-cols-7"
4
4
  @click="ui.setOpenContextMenu('')"
5
- @click.right.prevent="ui.setOpenContextMenu('')"
6
- >
7
- <div class="col-span-3 lg:col-span-2 flex">
5
+ @click.right.prevent="ui.setOpenContextMenu('')">
6
+ <div class="col-span-3 flex lg:col-span-2">
8
7
  <Checkbox
9
8
  class="ml-0 mr-4"
10
- :toggled="props.task.selected"
11
- @valueUpdate="ui.toggleAccountSelected(props.task._id)"
12
- />
13
- <h4 class="mx-auto text-white" @click="copy(props.task.email)">
14
- {{ props.task.email }}
9
+ :toggled="props.account.selected"
10
+ @valueUpdate="ui.toggleAccountSelected(props.account.id)" />
11
+ <h4 class="mx-auto text-white" @click="copy(props.account.email)">
12
+ {{ props.account.email }}
15
13
  </h4>
16
14
  </div>
17
- <div class="col-span-2 hidden ipadlg:block" @click="copy(props.task.password)">
15
+ <div class="col-span-2 hidden md:block" @click="copy(props.account.password)">
18
16
  <h4 class="text-white">
19
- {{ props.task.privacy ? "•".repeat(props.task.password.length) : props.task.password }}
17
+ {{ props.account.privacy ? "•".repeat(props.account.password.length) : props.account.password }}
20
18
  </h4>
21
19
  </div>
22
20
  <div class="col-span-1">
23
- <h4 v-if="props.task.enabled" class="text-green-400 flex justify-center">
24
- <img width="12px" height="12px" class="green" src="/img/controls/enable.svg" />
21
+ <h4 v-if="props.account.enabled" class="flex justify-center text-green-400">
22
+ <img class="green h-3 w-3" src="/img/controls/enable.svg" />
25
23
  </h4>
26
- <h4 v-else class="text-red-400 flex justify-center">
27
- <img width="12px" height="12px" class="fill-red-400" src="/img/close.svg" />
24
+ <h4 v-else class="flex justify-center text-red-400">
25
+ <img class="h-3 w-3 fill-red-400" src="/img/close.svg" />
28
26
  </h4>
29
27
  </div>
30
28
 
31
29
  <div class="col-span-1 hidden lg:block">
32
- <h4 class="text-white flex justify-center gap-1">
33
- <TagLabel v-for="tag in props.task.tags" :key="tag" :text="tag" />
30
+ <h4 class="flex justify-center gap-1 text-white">
31
+ <TagLabel v-for="tag in props.account.tags" :key="tag" :text="tag" />
34
32
  </h4>
35
33
  </div>
36
34
 
37
35
  <div class="col-span-1 flex">
38
- <ul class="task-buttons bg-dark-600 px-2 rounded-full shadow-3xl py-1 items-center">
36
+ <ul class="account-buttons">
39
37
  <li>
40
38
  <button @click="edit">
41
39
  <EditIcon />
42
40
  </button>
43
41
  </li>
44
- <li v-if="props.task.enabled">
42
+ <li v-if="props.account.enabled">
45
43
  <button @click="disable">
46
- <img width="16px" height="16px" class="mt-0.5" src="/img/controls/disable.svg" />
44
+ <img class="h-4 w-4" src="/img/controls/disable.svg" />
47
45
  </button>
48
46
  </li>
49
47
  <li v-else>
50
48
  <button @click="enable">
51
- <img width="16px" height="16px" class="mt-0.5" src="/img/controls/enable.svg" />
49
+ <img class="h-4 w-4" src="/img/controls/enable.svg" />
52
50
  </button>
53
51
  </li>
54
52
  </ul>
@@ -59,33 +57,111 @@
59
57
  h4 {
60
58
  @apply text-center;
61
59
  }
62
- .task-buttons {
63
- @apply flex mx-auto gap-x-3;
64
- svg {
65
- width: 15px;
66
- height: 15px;
60
+ .account-buttons {
61
+ @apply mx-auto flex items-center justify-center rounded border border-dark-650 bg-dark-500;
62
+ padding: 3px;
63
+ gap: 2px;
64
+
65
+ button {
66
+ @apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
67
+ background: transparent;
68
+ width: 28px;
69
+ height: 28px;
70
+ color: oklch(0.82 0 0);
71
+
72
+ &:hover {
73
+ background: rgba(255, 255, 255, 0.1);
74
+ color: #ffffff;
75
+ transform: scale(1.05);
76
+ }
77
+
78
+ &:active {
79
+ background: rgba(255, 255, 255, 0.2);
80
+ transform: scale(0.95);
81
+ }
82
+ }
83
+
84
+ svg,
85
+ img {
86
+ width: 16px;
87
+ height: 16px;
88
+ position: relative;
89
+ z-index: 1;
67
90
  }
68
- }
69
91
 
70
- svg {
71
- width: 15px;
72
- height: 15px;
92
+ svg path {
93
+ fill: currentColor;
94
+ }
73
95
  }
74
96
 
97
+ // Tablet optimization
75
98
  @media (max-width: 1024px) {
76
99
  h4 {
77
100
  font-size: 10px !important;
78
101
  }
79
- .task-buttons {
80
- @apply gap-x-3;
102
+
103
+ .account-buttons {
104
+ padding: 3px;
105
+ gap: 2px;
106
+
107
+ button {
108
+ width: 26px;
109
+ height: 26px;
110
+ }
111
+
112
+ svg,
113
+ img {
114
+ width: 14px;
115
+ height: 14px;
116
+ }
81
117
  }
82
- .task-id {
118
+
119
+ .account-id {
83
120
  font-size: 6px !important;
84
121
  margin-right: -12px;
85
122
  margin-top: 20px;
86
123
  }
87
- .task-id-alt {
88
- font-size: 7px !important;
124
+ }
125
+
126
+ // Mobile optimization
127
+ @media (max-width: 768px) {
128
+ .account-buttons {
129
+ padding: 2px;
130
+ gap: 1px;
131
+
132
+ button {
133
+ width: 22px;
134
+ height: 22px;
135
+ }
136
+
137
+ svg,
138
+ img {
139
+ width: 12px;
140
+ height: 12px;
141
+ }
142
+ }
143
+ }
144
+
145
+ // iPhone vertical (portrait) specific
146
+ @media (max-width: 480px) and (orientation: portrait) {
147
+ .account-buttons {
148
+ padding: 2px;
149
+ gap: 1px;
150
+
151
+ button {
152
+ width: 18px;
153
+ height: 18px;
154
+
155
+ &:hover {
156
+ transform: scale(1.1);
157
+ }
158
+ }
159
+
160
+ svg,
161
+ img {
162
+ width: 10px;
163
+ height: 10px;
164
+ }
89
165
  }
90
166
  }
91
167
  </style>
@@ -94,12 +170,12 @@ import { Row } from "@/components/Table";
94
170
  import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
95
171
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
96
172
  import { useUIStore } from "@/stores/ui";
97
- import TagLabel from "@/components/editors/TagLabel.vue";
173
+ import TagLabel from "@/components/Editors/TagLabel.vue";
98
174
 
99
175
  const ui = useUIStore();
100
176
 
101
177
  const props = defineProps({
102
- task: { type: Object }
178
+ account: { type: Object }
103
179
  });
104
180
 
105
181
  const copy = (txt) => {
@@ -107,10 +183,10 @@ const copy = (txt) => {
107
183
  navigator.clipboard.writeText(txt);
108
184
  ui.showSuccess("Copied text");
109
185
  };
110
- const enable = async () => await ui.addAccount({ ...props.task, enabled: true });
111
- const disable = async () => await ui.addAccount({ ...props.task, enabled: false });
186
+ const enable = async () => await ui.addAccount({ ...props.account, enabled: true });
187
+ const disable = async () => await ui.addAccount({ ...props.account, enabled: false });
112
188
  const edit = () => {
113
- ui.currentlyEditing = props.task;
189
+ ui.currentlyEditing = props.account;
114
190
  ui.toggleModal("create-account");
115
191
  };
116
192
  </script>
@@ -6,63 +6,85 @@
6
6
  </template>
7
7
 
8
8
  <div>
9
- <div class="my-3 grid grid-cols-12 gap-3 mt-7 mb-4">
10
- <div class="input-wrapper col-span-8 z-inf">
11
- <label class="label-override">Account Tag </label>
12
- <div :class="`input-default ${errors.includes('accountTag') ? 'error' : ''}`">
13
- <Dropdown
14
- class="text-xs w-64 h-8"
15
- :default="ui.profile.accountTags[0]"
16
- :options="ui.profile.accountTags"
17
- :onClick="(f) => (creatorConfig.tag = f)"
18
- :capitalize="true"
19
- rightAmount="right-6 lg:right-2 top-2"
20
- />
21
- </div>
9
+ <div class="form-grid mb-4 mt-7">
10
+ <div class="input-wrapper relative-positioned z-tooltip col-span-8">
11
+ <label class="label-override mb-2">
12
+ Account Tag
13
+ <TagIcon />
14
+ </label>
15
+ <Dropdown
16
+ class="input-default dropdown w-full p-4"
17
+ :default="ui.profile.tags[0]"
18
+ :options="ui.profile.tags"
19
+ :onClick="(f) => (creatorConfig.tag = f)"
20
+ :capitalize="true"
21
+ :allowDefault="false"
22
+ :chosen="creatorConfig.tag" />
22
23
  </div>
23
24
 
24
25
  <div class="input-wrapper col-span-4">
25
- <label class="label-override">Threads</label>
26
+ <label class="label-override mb-2">
27
+ Threads
28
+ <EditIcon />
29
+ </label>
26
30
  <div :class="`input-default ${errors.includes('threads') ? 'error' : ''}`">
27
31
  <input placeholder="1" type="number" min="1" max="50" v-model="creatorConfig.threads" />
32
+ <div class="input-incrementer">
33
+ <button @click="creatorConfig.threads++">
34
+ <UpIcon />
35
+ </button>
36
+ <button @click="if (creatorConfig.threads > 1) creatorConfig.threads--;">
37
+ <DownIcon />
38
+ </button>
39
+ </div>
28
40
  </div>
29
41
  </div>
30
42
  <div class="input-wrapper col-span-8">
31
- <label class="label-override">Email catchall</label>
43
+ <label class="label-override mb-2">
44
+ Email catchall
45
+ <MailIcon />
46
+ </label>
32
47
  <div :class="`input-default ${errors.includes('catchall') ? 'error' : ''}`">
33
- <input placeholder="mycatchall.com" v-model="creatorConfig.catchall" />
48
+ <input placeholder="example.com" v-model="creatorConfig.catchall" />
34
49
  </div>
35
50
  </div>
36
51
  <div class="input-wrapper col-span-4">
37
- <label class="label-override">Catchall amount</label>
52
+ <label class="label-override mb-2">
53
+ Catchall amount
54
+ <BagIcon />
55
+ </label>
38
56
  <div :class="`input-default ${errors.includes('number') ? 'error' : ''}`">
39
57
  <input placeholder="1" type="number" min="0" max="5000" v-model="creatorConfig.number" />
58
+ <div class="input-incrementer">
59
+ <button @click="creatorConfig.number++">
60
+ <UpIcon />
61
+ </button>
62
+ <button @click="if (creatorConfig.number > 0) creatorConfig.number--;">
63
+ <DownIcon />
64
+ </button>
65
+ </div>
40
66
  </div>
41
67
  </div>
42
68
  <div class="input-wrapper col-span-12">
43
- <div class="flex items-center gap-2">
44
- <label class="label-override">Emails </label>
69
+ <label class="label-override mb-2">
70
+ Emails
71
+ <MailIcon />
72
+ </label>
73
+ <div :class="`${errors.includes('emails') ? 'error-border' : ''}`">
74
+ <textarea
75
+ v-model="creatorConfig.emails"
76
+ class="proxy-editor"
77
+ spellcheck="false"
78
+ style="max-height: 250px; min-height: 150px"
79
+ placeholder="Enter emails here - One per line"></textarea>
45
80
  </div>
46
- <!-- <div :class="`input-default ${errors.includes('email') ? 'error' : ''}`"> -->
47
- <!-- <input placeholder="email@example.com" type="email" v-model="account.email" /> -->
48
-
49
- <CodeEditor
50
- v-model="creatorConfig.emails"
51
- width="100%"
52
- spellcheck="false"
53
- :hide_header="true"
54
- :language_selector="false"
55
- :languages="[['text']]"
56
- theme="dark"
57
- :class="`${errors.includes('emails') ? 'error' : ''} max-h-60`"
58
- ></CodeEditor>
59
81
  </div>
60
82
  </div>
61
83
  <button
62
- class="button-default hover:opacity-70 active:opacity-50 bg-dark-400 w-48 text-xs flex items-center justify-center gap-x-2 ml-auto mt-4"
63
- @click="done()"
64
- >
65
- Start <EditIcon />
84
+ class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
85
+ @click="done()">
86
+ Start
87
+ <EditIcon />
66
88
  </button>
67
89
  </div>
68
90
  </Modal>
@@ -78,16 +100,43 @@
78
100
  border-color: rgb(238 130 130) !important;
79
101
  }
80
102
 
103
+ .error-border {
104
+ border: 2px solid rgb(238 130 130) !important;
105
+ border-radius: 8px;
106
+ }
107
+
108
+ /* Proxy editor styles */
109
+ .proxy-editor {
110
+ width: 100%;
111
+ background-color: oklch(0.19 0 0);
112
+ color: oklch(0.90 0 0);
113
+ font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
114
+ padding: 12px;
115
+ border: none;
116
+ resize: none;
117
+ font-size: 14px;
118
+ line-height: 1.6;
119
+ tab-size: 4;
120
+ outline: none;
121
+ border: 1px solid oklch(0.26 0 0);
122
+ border-radius: 8px;
123
+ overflow: auto;
124
+ }
125
+
126
+ .proxy-editor:focus {
127
+ border-color: oklch(0.28 0 0);
128
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
129
+ }
130
+
81
131
  .z-inf {
82
132
  z-index: 99999999999999;
83
133
  }
84
134
  </style>
85
135
  <script setup>
86
136
  import Modal from "@/components/ui/Modal.vue";
87
- import { EditIcon } from "@/components/icons";
137
+ import { EditIcon, TagIcon, SpinnerIcon, UpIcon, DownIcon, MailIcon, BagIcon } from "@/components/icons";
88
138
  import { useUIStore } from "@/stores/ui";
89
139
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
90
- import CodeEditor from "simple-code-editor";
91
140
 
92
141
  import { ref } from "vue";
93
142
 
@@ -96,7 +145,7 @@ const creatorConfig = ref({
96
145
  threads: 5,
97
146
  catchall: "",
98
147
  emails: "",
99
- tag: ui.profile.accountTags[0],
148
+ tag: ui.profile.tags[0],
100
149
  number: 0
101
150
  });
102
151
 
@@ -1,59 +1,62 @@
1
1
  <template>
2
2
  <Table>
3
- <Header class="text-center grid-cols-5 ipadlg:grid-cols-7">
4
- <div class="lg:col-span-2 col-span-3 flex">
3
+ <Header class="sticky top-0 z-10 grid-cols-5 bg-dark-400 text-center md:grid-cols-7">
4
+ <div class="col-span-3 flex lg:col-span-2">
5
5
  <Checkbox
6
6
  class="mr-3"
7
7
  :toggled="ui.mainCheckbox.accounts"
8
8
  @valueUpdate="ui.toggleMainCheckbox('accounts')"
9
- />
9
+ :isHeader="true" />
10
10
  <div class="mx-auto flex items-center" @click="ui.toggleSort('eventId')">
11
- <img src="/img/mail.svg" class="mr-0 ipadlg:mr-3" />
12
- <h4 class="hidden ipadlg:flex">Email</h4>
11
+ <MailIcon class="mr-0 h-4 w-4 md:mr-3" />
12
+ <h4 class="hidden md:flex">Email</h4>
13
13
  </div>
14
14
  </div>
15
- <div class="col-span-2 items-center justify-center hidden ipadlg:flex" v-once>
16
- <img src="/img/key.svg" class="mr-0 ipadlg:mr-3" />
17
- <h4 class="hidden ipadlg:flex">Password</h4>
15
+ <div class="col-span-2 hidden items-center justify-center md:flex" v-once>
16
+ <KeyIcon class="mr-0 h-4 w-4 md:mr-3" />
17
+ <h4 class="hidden md:flex">Password</h4>
18
18
  </div>
19
19
  <div class="col-span-1 flex items-center justify-center" v-once>
20
- <img src="/img/controls/enable.svg" width="14" class="mr-0 ipadlg:mr-3" />
21
- <h4 class="hidden ipadlg:flex">Enabled</h4>
20
+ <CheckmarkIcon class="mr-0 h-4 w-4 md:mr-3" />
21
+ <h4 class="hidden md:flex">Enabled</h4>
22
22
  </div>
23
- <div class="col-span-1 hidden lg:flex items-center justify-center" v-once>
24
- <TicketIcon class="mr-0 ipadlg:mr-3" />
25
- <h4 class="hidden ipadlg:flex">Tags</h4>
23
+ <div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
24
+ <TicketIcon class="mr-0 h-4 w-4 md:mr-3" />
25
+ <h4 class="hidden md:flex">Tags</h4>
26
26
  </div>
27
27
  <div class="col-span-1 flex items-center justify-center" v-once>
28
- <ClickIcon class="mr-0 ipadlg:mr-3" />
29
- <h4 class="hidden ipadlg:flex">Actions</h4>
28
+ <ClickIcon class="mr-0 h-4 w-4 md:mr-3" />
29
+ <h4 class="hidden md:flex">Actions</h4>
30
30
  </div>
31
31
  </Header>
32
- <div v-if="toRender.length > 0">
32
+ <div
33
+ v-if="toRender.length != 0"
34
+ class="hidden-scrollbars stop-pan overflow-y-auto overflow-x-hidden"
35
+ :style="{ maxHeight: dynamicTableHeight }">
33
36
  <RecycleScroller
34
37
  :items="toRender"
35
38
  :item-size="64"
36
- key-field="_id"
37
- class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y-2 divide-border max-h-big overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
38
- >
39
+ key-field="index"
40
+ class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650">
39
41
  <template #default="props">
40
- <div class="task" :key="i[props.item._id]">
42
+ <div class="account" :key="`account-${props.item.id || props.item.index}`">
41
43
  <Account
42
- @click="i[props.item._id]++"
43
- :class="[props.item.index % 2 == 1 ? 'bg-dark-500' : 'bg-dark-550']"
44
- :task="props.item"
45
- />
44
+ @click="i[props.item.index]++"
45
+ :class="props.item.index % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
46
+ :account="props.item" />
46
47
  </div>
47
48
  </template>
48
49
  </RecycleScroller>
49
50
  </div>
50
- <div v-else class="flex justify-center text-light-400 py-2 bg-dark-500 border-b-2 border-border">
51
- No accounts found
51
+ <div v-else class="empty-state flex flex-col items-center justify-center bg-dark-400 py-8 text-center">
52
+ <MailIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
53
+ <p class="text-sm text-light-400">No accounts found</p>
54
+ <p class="mt-1 text-xs text-light-500">Create accounts to get started</p>
52
55
  </div>
53
56
  </Table>
54
57
  </template>
55
58
  <style lang="scss" scoped>
56
- .task {
59
+ .account {
57
60
  height: 64px;
58
61
  }
59
62
  h4 {
@@ -63,25 +66,90 @@ h4 {
63
66
  .stop-pan {
64
67
  touch-action: pan-y pan-up pan-down;
65
68
  }
69
+
70
+ .empty-state {
71
+ color: #969696;
72
+ font-size: 14px;
73
+ font-weight: 500;
74
+ }
66
75
  </style>
67
76
  <script setup>
68
77
  import { Table, Header } from "@/components/Table";
69
- import { EventIcon, TicketIcon, StatusIcon, ClickIcon, DownIcon } from "@/components/icons";
78
+ import {
79
+ EventIcon,
80
+ TicketIcon,
81
+ StatusIcon,
82
+ ClickIcon,
83
+ DownIcon,
84
+ MailIcon,
85
+ KeyIcon,
86
+ CheckmarkIcon
87
+ } from "@/components/icons";
70
88
  import Account from "./Account.vue";
71
89
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
72
90
  import { useUIStore } from "@/stores/ui";
73
- import { computed, ref } from "vue";
91
+ import { computed, ref, onMounted, onUnmounted } from "vue";
74
92
 
75
93
  const props = defineProps({
76
- tasks: { type: Object }
94
+ accounts: { type: Object }
77
95
  });
78
- const i = ref({});
79
- props.tasks.forEach((t) => (i.value[t._id] = 0));
80
-
81
96
  const ui = useUIStore();
82
97
 
98
+ const i = ref({});
83
99
  const toRender = computed(() => {
84
100
  let c = 0;
85
- return props.tasks.map((t) => ({ ...t, index: c++ }));
101
+ const rendered = props.accounts.map((t) => ({ ...t, index: c++ }));
102
+
103
+ // Initialize reactive refs for click tracking
104
+ rendered.forEach((t) => {
105
+ if (t.id && !(t.id in i.value)) {
106
+ i.value[t.id] = 0;
107
+ }
108
+ if (!(t.index in i.value)) {
109
+ i.value[t.index] = 0;
110
+ }
111
+ });
112
+
113
+ return rendered;
114
+ });
115
+
116
+ // Dynamic height calculation for perfect item fitting
117
+ const windowHeight = ref(window.innerHeight);
118
+ const windowWidth = ref(window.innerWidth);
119
+
120
+ const updateDimensions = () => {
121
+ windowHeight.value = window.innerHeight;
122
+ windowWidth.value = window.innerWidth;
123
+ };
124
+
125
+ onMounted(() => {
126
+ window.addEventListener("resize", updateDimensions);
127
+ });
128
+
129
+ onUnmounted(() => {
130
+ window.removeEventListener("resize", updateDimensions);
131
+ });
132
+
133
+ const dynamicTableHeight = computed(() => {
134
+ // Calculate available space for accounts table with conservative buffer
135
+ const headerHeight = 60; // Header + navbar
136
+ const titleHeight = 50; // Accounts title and controls
137
+ const searchHeight = 50; // Search and filter controls
138
+ const margins = windowWidth.value >= 1024 ? 40 : 25;
139
+ const bufferSpace = 50; // Conservative buffer to prevent partial items
140
+
141
+ const totalUsedSpace = headerHeight + titleHeight + searchHeight + margins + bufferSpace;
142
+ const availableHeight = windowHeight.value - totalUsedSpace;
143
+
144
+ // Account row height is always 64px
145
+ const rowHeight = 64;
146
+ const minRowsToShow = 2;
147
+ const minHeight = minRowsToShow * rowHeight;
148
+
149
+ // Calculate exact number of complete rows that fit with conservative approach
150
+ const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
151
+ const exactHeight = maxCompleteRows * rowHeight;
152
+
153
+ return exactHeight + "px";
86
154
  });
87
155
  </script>