@necrolab/dashboard 0.4.39 → 0.4.41

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.
@@ -7,48 +7,77 @@
7
7
 
8
8
  <div>
9
9
  <div class="form-grid 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>
10
+ <div class="input-wrapper col-span-8" style="z-index: 200 !important; position: relative">
11
+ <label class="label-override mb-2"
12
+ >Account Tag
13
+ <TagIcon />
14
+ </label>
15
+ <Dropdown
16
+ class="input-default dropdown p-4 w-full"
17
+ :default="ui.profile.accountTags[0]"
18
+ :options="ui.profile.accountTags"
19
+ :onClick="(f) => (creatorConfig.tag = f)"
20
+ :capitalize="true"
21
+ :allowDefault="false"
22
+ :chosen="creatorConfig.tag"
23
+ />
22
24
  </div>
23
25
 
24
26
  <div class="input-wrapper col-span-4">
25
- <label class="label-override">Threads</label>
27
+ <label class="label-override mb-2"
28
+ >Threads
29
+ <EditIcon />
30
+ </label>
26
31
  <div :class="`input-default ${errors.includes('threads') ? 'error' : ''}`">
27
32
  <input placeholder="1" type="number" min="1" max="50" v-model="creatorConfig.threads" />
33
+ <div class="input-incrementer">
34
+ <button @click="creatorConfig.threads++">
35
+ <UpIcon />
36
+ </button>
37
+ <button @click="if (creatorConfig.threads > 1) creatorConfig.threads--;">
38
+ <DownIcon />
39
+ </button>
40
+ </div>
28
41
  </div>
29
42
  </div>
30
43
  <div class="input-wrapper col-span-8">
31
- <label class="label-override">Email catchall</label>
44
+ <label class="label-override mb-2"
45
+ >Email catchall
46
+ <MailIcon />
47
+ </label>
32
48
  <div :class="`input-default ${errors.includes('catchall') ? 'error' : ''}`">
33
- <input placeholder="mycatchall.com" v-model="creatorConfig.catchall" />
49
+ <input placeholder="example.com" v-model="creatorConfig.catchall" />
34
50
  </div>
35
51
  </div>
36
52
  <div class="input-wrapper col-span-4">
37
- <label class="label-override">Catchall amount</label>
53
+ <label class="label-override mb-2"
54
+ >Catchall amount
55
+ <BagIcon />
56
+ </label>
38
57
  <div :class="`input-default ${errors.includes('number') ? 'error' : ''}`">
39
58
  <input placeholder="1" type="number" min="0" max="5000" v-model="creatorConfig.number" />
59
+ <div class="input-incrementer">
60
+ <button @click="creatorConfig.number++">
61
+ <UpIcon />
62
+ </button>
63
+ <button @click="if (creatorConfig.number > 0) creatorConfig.number--;">
64
+ <DownIcon />
65
+ </button>
66
+ </div>
40
67
  </div>
41
68
  </div>
42
69
  <div class="input-wrapper col-span-12">
43
- <div class="flex items-center gap-2">
44
- <label class="label-override">Emails </label>
45
- </div>
70
+ <label class="label-override mb-2"
71
+ >Emails
72
+ <MailIcon />
73
+ </label>
46
74
  <div :class="`${errors.includes('emails') ? 'error-border' : ''}`">
47
75
  <textarea
48
76
  v-model="creatorConfig.emails"
49
77
  class="proxy-editor"
50
78
  spellcheck="false"
51
79
  style="max-height: 250px; min-height: 150px"
80
+ placeholder="Enter emails here - One per line"
52
81
  ></textarea>
53
82
  </div>
54
83
  </div>
@@ -97,8 +126,8 @@
97
126
  }
98
127
 
99
128
  .proxy-editor:focus {
100
- border-color: #5d7cc0;
101
- box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);
129
+ border-color: #44454b;
130
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
102
131
  }
103
132
 
104
133
  .z-inf {
@@ -107,7 +136,7 @@
107
136
  </style>
108
137
  <script setup>
109
138
  import Modal from "@/components/ui/Modal.vue";
110
- import { EditIcon } from "@/components/icons";
139
+ import { EditIcon, TagIcon, SpinnerIcon, UpIcon, DownIcon, MailIcon, BagIcon } from "@/components/icons";
111
140
  import { useUIStore } from "@/stores/ui";
112
141
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
113
142
 
@@ -30,19 +30,20 @@
30
30
  <h4 class="hidden ipadlg:flex">Actions</h4>
31
31
  </div>
32
32
  </Header>
33
- <div v-if="toRender.length > 0">
33
+ <div v-if="toRender.length != 0">
34
34
  <RecycleScroller
35
35
  :items="toRender"
36
36
  :item-size="64"
37
- key-field="_id"
38
- class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 max-h-big overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
37
+ key-field="index"
38
+ class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
39
+ :style="{ maxHeight: dynamicTableHeight }"
39
40
  >
40
41
  <template #default="props">
41
- <div class="task" :key="i[props.item._id]">
42
+ <div class="account" :key="`account-${props.item._id || props.item.index}`">
42
43
  <Account
43
- @click="i[props.item._id]++"
44
- :style="{ backgroundColor: props.item.index % 2 == 1 ? '#35363c' : '#2e2f34' }"
45
- :task="props.item"
44
+ @click="i[props.item.index]++"
45
+ :style="{ backgroundColor: props.item.index % 2 == 1 ? '#1a1b1e' : '#242529' }"
46
+ :account="props.item"
46
47
  />
47
48
  </div>
48
49
  </template>
@@ -56,7 +57,7 @@
56
57
  </Table>
57
58
  </template>
58
59
  <style lang="scss" scoped>
59
- .task {
60
+ .account {
60
61
  height: 64px;
61
62
  }
62
63
  h4 {
@@ -88,18 +89,68 @@ import {
88
89
  import Account from "./Account.vue";
89
90
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
90
91
  import { useUIStore } from "@/stores/ui";
91
- import { computed, ref } from "vue";
92
+ import { computed, ref, onMounted, onUnmounted } from "vue";
92
93
 
93
94
  const props = defineProps({
94
95
  accounts: { type: Object }
95
96
  });
96
- const i = ref({});
97
- props.accounts.forEach((t) => (i.value[t._id] = 0));
98
-
99
97
  const ui = useUIStore();
100
98
 
99
+ const i = ref({});
101
100
  const toRender = computed(() => {
102
101
  let c = 0;
103
- return props.accounts.map((t) => ({ ...t, index: c++ }));
102
+ const rendered = props.accounts.map((t) => ({ ...t, index: c++ }));
103
+
104
+ // Initialize reactive refs for click tracking
105
+ rendered.forEach((t) => {
106
+ if (t._id && !(t._id in i.value)) {
107
+ i.value[t._id] = 0;
108
+ }
109
+ if (!(t.index in i.value)) {
110
+ i.value[t.index] = 0;
111
+ }
112
+ });
113
+
114
+ return rendered;
115
+ });
116
+
117
+ // Dynamic height calculation for perfect item fitting
118
+ const windowHeight = ref(window.innerHeight);
119
+ const windowWidth = ref(window.innerWidth);
120
+
121
+ const updateDimensions = () => {
122
+ windowHeight.value = window.innerHeight;
123
+ windowWidth.value = window.innerWidth;
124
+ };
125
+
126
+ onMounted(() => {
127
+ window.addEventListener("resize", updateDimensions);
128
+ });
129
+
130
+ onUnmounted(() => {
131
+ window.removeEventListener("resize", updateDimensions);
132
+ });
133
+
134
+ const dynamicTableHeight = computed(() => {
135
+ // Calculate available space for accounts table with conservative buffer
136
+ const headerHeight = 60; // Header + navbar
137
+ const titleHeight = 50; // Accounts title and controls
138
+ const searchHeight = 50; // Search and filter controls
139
+ const margins = windowWidth.value >= 1024 ? 40 : 25;
140
+ const bufferSpace = 50; // Conservative buffer to prevent partial items
141
+
142
+ const totalUsedSpace = headerHeight + titleHeight + searchHeight + margins + bufferSpace;
143
+ const availableHeight = windowHeight.value - totalUsedSpace;
144
+
145
+ // Account row height is always 64px
146
+ const rowHeight = 64;
147
+ const minRowsToShow = 2;
148
+ const minHeight = minRowsToShow * rowHeight;
149
+
150
+ // Calculate exact number of complete rows that fit with conservative approach
151
+ const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
152
+ const exactHeight = maxCompleteRows * rowHeight;
153
+
154
+ return exactHeight + "px";
104
155
  });
105
156
  </script>
@@ -7,27 +7,32 @@
7
7
  </template>
8
8
 
9
9
  <div>
10
- <div class="my-3 grid grid-cols-12 gap-3 mt-7 mb-4">
11
- <div class="input-wrapper col-span-4 z-10">
12
- <label class="label-override">Profile Tag
10
+ <div class="grid grid-cols-12 gap-3 my-3">
11
+ <!-- Profile tag -->
12
+ <div class="input-wrapper col-span-4" style="z-index: 200 !important; position: relative">
13
+ <label class="label-override mb-2">Profile Tag
13
14
  <TagIcon />
14
15
  </label>
15
16
  <Dropdown
16
- :class="`input-default dropdown w-full ${errors.includes('profileTag') ? 'error' : ''}`"
17
+ :class="`input-default dropdown p-4 w-full ${errors.includes('profileTag') ? 'error' : ''}`"
17
18
  :default="ui.profile.accountTags[0]"
18
19
  :options="ui.profile.accountTags"
19
20
  :onClick="(f) => (account.tag = f)"
20
21
  :capitalize="true"
22
+ :allowDefault="false"
23
+ :chosen="account.tag"
21
24
  />
22
25
  </div>
23
- <div class="input-wrapper col-span-8">
24
- <label class="label-override">Email
26
+
27
+ <!-- Email -->
28
+ <div class="input-wrapper col-span-8 z-0">
29
+ <label class="label-override mb-2">Email
25
30
  <MailIcon />
26
31
  </label>
27
- <div :class="`input-default required ${errors.includes('email') ? 'error' : ''}`">
32
+ <div :class="`input-default ${errors.includes('email') ? 'error' : ''}`">
28
33
  <input
29
34
  placeholder="email@example.com"
30
- type="text"
35
+ type="email"
31
36
  v-model="account.email"
32
37
  required
33
38
  autocomplete="new-password"
@@ -41,28 +46,32 @@
41
46
  />
42
47
  </div>
43
48
  </div>
44
- </div>
45
- <div class="input-wrapper col-span-12">
46
- <label class="label-override">Password
47
- <KeyIcon />
48
- </label>
49
- <div :class="`input-default required ${errors.includes('password') ? 'error' : ''}`">
50
- <input
51
- placeholder="***********"
52
- v-model="account.password"
53
- required
54
- autocomplete="off"
55
- name="account_password_disableautocomplete"
56
- />
49
+
50
+ <!-- Password -->
51
+ <div class="input-wrapper col-span-12 z-0">
52
+ <label class="label-override mb-2">Password
53
+ <KeyIcon />
54
+ </label>
55
+ <div :class="`input-default ${errors.includes('password') ? 'error' : ''}`">
56
+ <input
57
+ placeholder="***********"
58
+ type="password"
59
+ v-model="account.password"
60
+ required
61
+ autocomplete="off"
62
+ name="account_password_disableautocomplete"
63
+ />
64
+ </div>
57
65
  </div>
58
66
  </div>
59
- <button
60
- 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"
61
- @click="done()"
62
- >
63
- Save <EditIcon />
64
- </button>
65
67
  </div>
68
+
69
+ <button
70
+ 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"
71
+ @click="done()"
72
+ >
73
+ Save <EditIcon />
74
+ </button>
66
75
  </Modal>
67
76
  </template>
68
77
  <style lang="scss" scoped>
@@ -71,6 +80,16 @@
71
80
  @apply flex;
72
81
  }
73
82
  }
83
+ .z-0 {
84
+ z-index: 0 !important;
85
+ }
86
+ .z-1 {
87
+ z-index: 1 !important;
88
+ }
89
+ .z-2 {
90
+ z-index: 2 !important;
91
+ }
92
+
74
93
  .error {
75
94
  border-width: 2px !important;
76
95
  border-color: rgb(238 130 130) !important;
@@ -78,7 +97,7 @@
78
97
  </style>
79
98
  <script setup>
80
99
  import Modal from "@/components/ui/Modal.vue";
81
- import { EditIcon, MailIcon, KeyIcon, ProfileIcon, TimerIcon, SandclockIcon, TagIcon } from "@/components/icons";
100
+ import { EditIcon, MailIcon, KeyIcon, ProfileIcon, TimerIcon, SandclockIcon, TagIcon, ScannerIcon } from "@/components/icons";
82
101
  import { useUIStore } from "@/stores/ui";
83
102
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
84
103
 
@@ -6,8 +6,7 @@
6
6
  class="mr-3"
7
7
  :toggled="ui.mainCheckbox.profiles"
8
8
  @valueUpdate="ui.toggleMainCheckbox('profiles')"
9
- :isHeader="true"
10
- />
9
+ :isHeader="true" />
11
10
  <div class="mx-auto flex items-center" @click="ui.toggleSort('eventId')">
12
11
  <ProfileIcon class="mr-0 ipadlg:mr-3 w-4 h-4" />
13
12
  <h4 class="hidden ipadlg:flex">Profile Name</h4>
@@ -19,7 +18,7 @@
19
18
  </div>
20
19
  <div class="col-span-1 flex items-center justify-center" v-once>
21
20
  <TimerIcon class="mr-0 ipadlg:mr-3 w-4 h-4" />
22
- <h4 class="hidden ipadlg:flex">Exp. Date</h4>
21
+ <h4 class="hidden ipadlg:flex">Expiration</h4>
23
22
  </div>
24
23
  <div class="col-span-1 flex items-center justify-center" v-once>
25
24
  <CheckmarkIcon class="mr-0 ipadlg:mr-3 w-4 h-4" />
@@ -39,15 +38,14 @@
39
38
  :items="toRender"
40
39
  :item-size="64"
41
40
  key-field="index"
42
- class="scroller max-h-big vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
43
- >
41
+ class="scroller vue-recycle-scroller ready direction-vertical flex flex-col divide-y divide-dark-650 overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
42
+ :style="{ maxHeight: dynamicTableHeight }">
44
43
  <template #default="props">
45
44
  <div class="profile" :key="`profile-${props.item._id || props.item.index}`">
46
45
  <Profile
47
46
  @click="i[props.item.index]++"
48
47
  :style="{ backgroundColor: props.item.index % 2 == 1 ? '#1a1b1e' : '#242529' }"
49
- :profile="props.item"
50
- />
48
+ :profile="props.item" />
51
49
  </div>
52
50
  </template>
53
51
  </RecycleScroller>
@@ -88,7 +86,7 @@ import { CartIcon, TicketIcon, ClickIcon, TimerIcon, ProfileIcon, CheckmarkIcon
88
86
  import Profile from "./Profile.vue";
89
87
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
90
88
  import { useUIStore } from "@/stores/ui";
91
- import { computed, ref } from "vue";
89
+ import { computed, ref, onMounted, onUnmounted } from "vue";
92
90
 
93
91
  const props = defineProps({
94
92
  profiles: { type: Object }
@@ -113,4 +111,44 @@ const toRender = computed(() => {
113
111
 
114
112
  return rendered;
115
113
  });
114
+
115
+ // Dynamic height calculation for perfect item fitting
116
+ const windowHeight = ref(window.innerHeight);
117
+ const windowWidth = ref(window.innerWidth);
118
+
119
+ const updateDimensions = () => {
120
+ windowHeight.value = window.innerHeight;
121
+ windowWidth.value = window.innerWidth;
122
+ };
123
+
124
+ onMounted(() => {
125
+ window.addEventListener("resize", updateDimensions);
126
+ });
127
+
128
+ onUnmounted(() => {
129
+ window.removeEventListener("resize", updateDimensions);
130
+ });
131
+
132
+ const dynamicTableHeight = computed(() => {
133
+ // Calculate available space for profiles table with conservative buffer
134
+ const headerHeight = 60; // Header + navbar
135
+ const titleHeight = 50; // Profiles title and controls
136
+ const searchHeight = 50; // Search and filter controls
137
+ const margins = windowWidth.value >= 1024 ? 40 : 25;
138
+ const bufferSpace = 50; // Conservative buffer to prevent partial items
139
+
140
+ const totalUsedSpace = headerHeight + titleHeight + searchHeight + margins + bufferSpace;
141
+ const availableHeight = windowHeight.value - totalUsedSpace;
142
+
143
+ // Profile row height is always 64px
144
+ const rowHeight = 64;
145
+ const minRowsToShow = 2;
146
+ const minHeight = minRowsToShow * rowHeight;
147
+
148
+ // Calculate exact number of complete rows that fit with conservative approach
149
+ const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
150
+ const exactHeight = maxCompleteRows * rowHeight;
151
+
152
+ return exactHeight + "px";
153
+ });
116
154
  </script>
@@ -92,10 +92,6 @@
92
92
  overflow: auto;
93
93
  }
94
94
 
95
- /* Enhance the text selection color */
96
- .code-editor::selection {
97
- background: rgba(98, 114, 164, 0.4);
98
- }
99
95
 
100
96
  .code-highlight {
101
97
  width: 100%;
@@ -1,37 +1,43 @@
1
1
  <template>
2
- <div class="flex text-white font-bold lg:gap-5 gap-1 lg:mb-2 mb-1" v-if="show" :key="key">
2
+ <div class="flex text-white font-bold lg:gap-5 gap-1 lg:mb-2 mb-1 min-h-[2.5rem]" v-if="show" :key="key">
3
3
  <div
4
4
  v-if="ui.queueStats.total"
5
- class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between"
5
+ class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between items-center min-w-0 h-8"
6
6
  >
7
- <h2 class="font-bold text-sm flex items-center gap-1 scale-100">
7
+ <h2 class="font-bold text-sm flex items-center gap-1 whitespace-nowrap">
8
8
  <img width="14px" src="@/assets/img/wildcard.svg" />Total MQM
9
9
  </h2>
10
- <span class="text-light-400 text-sm font-black flex justify-center">{{ ui.queueStats.total }} </span>
10
+ <span class="text-light-400 text-xs font-black flex items-center justify-center whitespace-nowrap">{{
11
+ ui.queueStats.total
12
+ }}</span>
11
13
  </div>
12
14
  <div
13
15
  v-if="ui.queueStats.queued"
14
- class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between"
16
+ class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between items-center min-w-0 h-8"
15
17
  >
16
- <h2 class="font-bold text-sm flex items-center gap-1"><SkiIcon />Queued</h2>
17
- <span class="text-light-400 text-sm font-black flex justify-center">{{ ui.queueStats.queued }} </span>
18
+ <h2 class="font-bold text-sm flex items-center gap-1 whitespace-nowrap"><SkiIcon />Queued</h2>
19
+ <span class="text-light-400 text-xs font-black flex items-center justify-center whitespace-nowrap">{{
20
+ ui.queueStats.queued
21
+ }}</span>
18
22
  </div>
19
23
  <div
20
24
  v-if="ui.queueStats.sleeping"
21
- class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between"
25
+ class="stats-card lg:mb-2 mb-1 text-sm rounded-xl lg:p-3 p-2 lg:gap-3 gap-2 flex justify-between items-center min-w-0 h-8"
22
26
  >
23
- <h2 class="font-bold text-sm flex items-center gap-1"><TimerIcon />Sleeping</h2>
24
- <span class="text-light-400 text-sm font-black flex justify-center">{{ ui.queueStats.sleeping }} </span>
27
+ <h2 class="font-bold text-sm flex items-center gap-1 whitespace-nowrap"><TimerIcon />Sleeping</h2>
28
+ <span class="text-light-400 text-xs font-black flex items-center justify-center whitespace-nowrap">{{
29
+ ui.queueStats.sleeping
30
+ }}</span>
25
31
  </div>
26
32
  <div
27
33
  v-if="ui.queueStats.nextQueuePasses.length > 0"
28
- class="stats-card lg:mb-5 mb-2 rounded-xl text-sm flex justify-between lg:p-3 p-2 lg:gap-3 gap-2"
34
+ class="stats-card lg:mb-5 mb-2 rounded-xl text-sm flex justify-between items-center lg:p-3 p-2 lg:gap-3 gap-2 min-w-0 h-8"
29
35
  >
30
- <h2 class="font-bold flex text-sm items-center gap-1">
36
+ <h2 class="font-bold flex text-sm items-center gap-1 whitespace-nowrap">
31
37
  <CartIcon /><span class="sm:block hidden">Next Passes</span>
32
38
  <span class="sm:hidden block">Pass</span>
33
39
  </h2>
34
- <span class="text-light-400 text-sm font-black">{{
40
+ <span class="text-light-400 text-xs font-black flex items-center text-right truncate">{{
35
41
  ui.queueStats.nextQueuePasses.slice(0, queuePassAmount).join(", ")
36
42
  }}</span>
37
43
  </div>
@@ -68,16 +74,16 @@ window.addEventListener("resize", () => (queuePassAmount.value = getQueuePassAmo
68
74
  .stats-card {
69
75
  @apply bg-dark-500 border border-dark-650 rounded;
70
76
  transition: all 0.15s ease;
71
-
77
+
72
78
  &:hover {
73
79
  @apply bg-dark-550 border-dark-700;
74
80
  }
75
-
81
+
76
82
  h2 {
77
83
  font-weight: 600;
78
84
  color: #d4d4d4;
79
85
  }
80
-
86
+
81
87
  span {
82
88
  font-weight: 700;
83
89
  color: #cccccc;