@necrolab/dashboard 0.4.221 → 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 -77
  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,64 +1,64 @@
1
1
  <template>
2
2
  <Row
3
- class="relative text-white grid-cols-7 lg:grid-cols-8"
3
+ class="relative grid-cols-7 text-white lg:grid-cols-8"
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.toggleProfileSelected(props.task._id)"
12
- />
9
+ :toggled="props.profile.selected"
10
+ @valueUpdate="ui.toggleProfileSelected(props.profile.id)" />
13
11
  <h4 class="mx-auto text-white">
14
- {{ props.task.profileName }}
12
+ {{ props.profile.profileName }}
15
13
  </h4>
16
14
  </div>
17
15
  <div class="col-span-1 lg:col-span-2">
18
- <h4 class="text-white flex justify-center items-center gap-2">
19
- <span class="hidden ipadlg:block">{{
20
- props.task.privacy
21
- ? props.task.cardNumber[0] +
22
- "•".repeat(props.task.cardNumber.length - 5) +
23
- props.task.cardNumber.slice(-4)
24
- : validateCard(props.task.cardNumber).formatted
25
- }}</span>
26
- <img width="22 px" :src="getAccountType()" />
16
+ <h4 class="flex items-center justify-center gap-2 text-white">
17
+ <span class="hidden sm:block">
18
+ {{
19
+ props.profile.privacy
20
+ ? props.profile.cardNumber[0] +
21
+ "•".repeat(props.profile.cardNumber.length - 5) +
22
+ props.profile.cardNumber.slice(-4)
23
+ : validateCard(props.profile.cardNumber).formatted
24
+ }}
25
+ </span>
26
+ <img class="h-6 w-6" :src="getAccountType()" />
27
27
  </h4>
28
28
  </div>
29
29
  <div class="col-span-1">
30
30
  <h4 class="text-white">{{ expDate() }}</h4>
31
31
  </div>
32
32
  <div class="col-span-1">
33
- <h4 v-if="props.task.enabled" class="text-green-400 flex justify-center">
34
- <img width="12px" height="12px" class="green" src="/img/controls/enable.svg" />
33
+ <h4 v-if="props.profile.enabled" class="flex justify-center text-green-400">
34
+ <img class="green h-3 w-3" src="/img/controls/enable.svg" />
35
35
  </h4>
36
- <h4 v-else class="text-red-400 flex justify-center">
37
- <img width="12px" height="12px" class="fill-red-400" src="/img/close.svg" />
36
+ <h4 v-else class="flex justify-center text-red-400">
37
+ <img class="h-3 w-3 fill-red-400" src="/img/close.svg" />
38
38
  </h4>
39
39
  </div>
40
40
 
41
41
  <div class="col-span-1 hidden lg:block">
42
- <h4 class="text-white flex justify-center gap-1">
43
- <TagLabel v-for="tag in props.task.tags" :key="tag" :text="tag" />
42
+ <h4 class="flex justify-center gap-1 text-white">
43
+ <TagLabel v-for="tag in props.profile.tags" :key="tag" :text="tag" />
44
44
  </h4>
45
45
  </div>
46
46
 
47
47
  <div class="col-span-1 flex">
48
- <ul class="task-buttons bg-dark-600 px-2 rounded-full shadow-3xl py-1 items-center">
48
+ <ul class="profile-buttons">
49
49
  <li>
50
50
  <button @click="edit">
51
51
  <EditIcon />
52
52
  </button>
53
53
  </li>
54
- <li v-if="props.task.enabled">
54
+ <li v-if="props.profile.enabled">
55
55
  <button @click="disable">
56
- <img width="16px" height="16px" class="mt-0.5" src="/img/controls/disable.svg" />
56
+ <img class="h-4 w-4" src="/img/controls/disable.svg" />
57
57
  </button>
58
58
  </li>
59
59
  <li v-else>
60
60
  <button @click="enable">
61
- <img width="16px" height="16px" class="mt-0.5" src="/img/controls/enable.svg" />
61
+ <img class="h-4 w-4" src="/img/controls/enable.svg" />
62
62
  </button>
63
63
  </li>
64
64
  <li>
@@ -76,28 +76,111 @@
76
76
  h4 {
77
77
  @apply text-center;
78
78
  }
79
- .task-buttons {
80
- @apply flex mx-auto gap-x-3;
81
- svg {
82
- width: 15px;
83
- height: 15px;
79
+ .profile-buttons {
80
+ @apply mx-auto flex items-center justify-center rounded border border-dark-650 bg-dark-500;
81
+ padding: 3px;
82
+ gap: 2px;
83
+
84
+ button {
85
+ @apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
86
+ background: transparent;
87
+ width: 28px;
88
+ height: 28px;
89
+ color: oklch(0.82 0 0);
90
+
91
+ &:hover {
92
+ background: rgba(255, 255, 255, 0.1);
93
+ color: #ffffff;
94
+ transform: scale(1.05);
95
+ }
96
+
97
+ &:active {
98
+ background: rgba(255, 255, 255, 0.2);
99
+ transform: scale(0.95);
100
+ }
101
+ }
102
+
103
+ svg,
104
+ img {
105
+ width: 16px;
106
+ height: 16px;
107
+ position: relative;
108
+ z-index: 1;
109
+ }
110
+
111
+ svg path {
112
+ fill: currentColor;
84
113
  }
85
114
  }
86
115
 
116
+ // Tablet optimization
87
117
  @media (max-width: 1024px) {
88
118
  h4 {
89
119
  font-size: 10px !important;
90
120
  }
91
- .task-buttons {
92
- @apply gap-x-3;
121
+
122
+ .profile-buttons {
123
+ padding: 3px;
124
+ gap: 2px;
125
+
126
+ button {
127
+ width: 26px;
128
+ height: 26px;
129
+ }
130
+
131
+ svg,
132
+ img {
133
+ width: 14px;
134
+ height: 14px;
135
+ }
93
136
  }
94
- .task-id {
137
+
138
+ .profile-id {
95
139
  font-size: 6px !important;
96
140
  margin-right: -12px;
97
141
  margin-top: 20px;
98
142
  }
99
- .task-id-alt {
100
- font-size: 7px !important;
143
+ }
144
+
145
+ // Mobile optimization
146
+ @media (max-width: 768px) {
147
+ .profile-buttons {
148
+ padding: 2px;
149
+ gap: 1px;
150
+
151
+ button {
152
+ width: 22px;
153
+ height: 22px;
154
+ }
155
+
156
+ svg,
157
+ img {
158
+ width: 12px;
159
+ height: 12px;
160
+ }
161
+ }
162
+ }
163
+
164
+ // iPhone vertical (portrait) specific
165
+ @media (max-width: 480px) and (orientation: portrait) {
166
+ .profile-buttons {
167
+ padding: 2px;
168
+ gap: 1px;
169
+
170
+ button {
171
+ width: 18px;
172
+ height: 18px;
173
+
174
+ &:hover {
175
+ transform: scale(1.1);
176
+ }
177
+ }
178
+
179
+ svg,
180
+ img {
181
+ width: 10px;
182
+ height: 10px;
183
+ }
101
184
  }
102
185
  }
103
186
  </style>
@@ -107,28 +190,30 @@ import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/compon
107
190
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
108
191
  import { useUIStore } from "@/stores/ui";
109
192
  import { validateCard } from "@/stores/utils";
110
- import TagLabel from "@/components/editors/TagLabel.vue";
193
+ import TagLabel from "@/components/Editors/TagLabel.vue";
111
194
 
112
195
  const ui = useUIStore();
113
196
 
114
197
  const props = defineProps({
115
- task: { type: Object }
198
+ profile: { type: Object }
116
199
  });
117
200
 
118
201
  const getAccountType = () => {
119
- var cn = props.task.cardNumber;
120
- if (cn.startsWith("4")) return `/img/banks/visa.svg`; // visa
121
- else if (cn.startsWith("3")) return `/img/banks/amex.svg`; // amex
202
+ var cn = props.profile.cardNumber;
203
+ if (cn.startsWith("4"))
204
+ return `/img/banks/visa.svg`; // visa
205
+ else if (cn.startsWith("3"))
206
+ return `/img/banks/amex.svg`; // amex
122
207
  else if (cn.startsWith("5")) return `/img/banks/mastercard.svg`; // master
123
208
  };
124
209
 
125
210
  const expDate = () =>
126
- props.task.privacy ? "••/••" : `${props.task.expMonth}/${props.task.expYear?.replace("20", "")}`;
127
- const enable = async () => await ui.addProfile({ ...props.task, enabled: true });
128
- const disable = async () => await ui.addProfile({ ...props.task, enabled: false });
211
+ props.profile.privacy ? "••/••" : `${props.profile.expMonth}/${props.profile.expYear?.replace("20", "")}`;
212
+ const enable = async () => await ui.addProfile({ ...props.profile, enabled: true });
213
+ const disable = async () => await ui.addProfile({ ...props.profile, enabled: false });
129
214
  const edit = () => {
130
- ui.currentlyEditing = props.task;
215
+ ui.currentlyEditing = props.profile;
131
216
  ui.toggleModal("create-profile");
132
217
  };
133
- const deleteProfile = async () => await ui.deleteProfile(props.task._id);
218
+ const deleteProfile = async () => await ui.deleteProfile(props.profile.id);
134
219
  </script>
@@ -1,43 +1,75 @@
1
1
  <template>
2
2
  <div>
3
- <div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown rounded-lg">
4
- <span @click="open = !open" class="flex justify-between items-center z-inf text-white">
3
+ <div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown rounded-lg" ref="dropdownRef">
4
+ <span @click="toggleOpen" class="flex justify-between items-center z-50 text-white">
5
5
  <div class="flex gap-3 justify-center">
6
6
  <img class="w-5" :src="`/flags/${current?.toLowerCase()}.svg`" />
7
7
  </div>
8
8
  </span>
9
- <div
10
- v-if="open && !disabled"
11
- class="dropdown-content special-dropdown snap-mandatory snap-y z-inf max-h-48 overflow-scroll hidden-scrollbars"
12
- >
9
+ <Teleport to="body">
13
10
  <div
14
- v-for="(country, i) in countries"
15
- v-bind:key="country"
16
- :class="`cursor-pointer w-12 snap-start ${i === 0 ? '' : 'my-2'}`"
17
- @click="set(country)"
18
- >
19
- <div class="flex gap-3 justify-between smooth-hover">
20
- <span class="text-sm">{{ country }} </span>
21
- <img class="w-5" :src="`/flags/${country?.toLowerCase()}.svg`" />
11
+ v-if="open && !disabled"
12
+ class="dropdown-content-portal special-dropdown"
13
+ :style="menuStyle"
14
+ @click.stop
15
+ @wheel.stop
16
+ @touchmove.stop>
17
+ <div
18
+ v-for="(country, i) in countries"
19
+ v-bind:key="country"
20
+ :class="`cursor-pointer w-12 ${i === 0 ? '' : 'my-2'}`"
21
+ @click="set(country)">
22
+ <div class="flex justify-center items-center smooth-hover">
23
+ <span class="text-sm">{{ country }}</span>
24
+ <img class="w-5 ml-3" :src="`/flags/${country?.toLowerCase()}.svg`" />
25
+ </div>
22
26
  </div>
23
27
  </div>
24
- </div>
28
+ </Teleport>
25
29
  </div>
26
30
  </div>
27
31
  </template>
28
32
 
29
33
  <script setup>
30
34
  import { ref, watch } from "vue";
35
+ import { useDropdownPosition } from "@/composables/useDropdownPosition";
36
+ import { useClickOutside } from "@/composables/useClickOutside";
37
+
31
38
  const countries = ["US", "CA", "DE", "FR", "DK"];
32
39
  const open = ref(false);
40
+ const dropdownRef = ref(null);
33
41
 
34
42
  const props = defineProps({
35
43
  value: { type: String },
36
44
  onClick: { type: Function },
37
45
  disabled: { type: Boolean, required: false }
38
46
  });
47
+
39
48
  const current = ref(props.value || "US");
40
49
 
50
+ // Use composables for positioning and click outside
51
+ const { menuStyle, updatePosition } = useDropdownPosition(dropdownRef, {
52
+ offset: { x: -17.6, y: 4 },
53
+ minWidth: 80,
54
+ maxHeight: 192,
55
+ zIndex: 99999999999999,
56
+ estimateHeight: () => Math.min(countries.length * 32, 192)
57
+ });
58
+
59
+ useClickOutside(dropdownRef, () => {
60
+ if (open.value) {
61
+ open.value = false;
62
+ }
63
+ });
64
+
65
+ const toggleOpen = () => {
66
+ if (props.disabled) return;
67
+ open.value = !open.value;
68
+ if (open.value) {
69
+ updatePosition();
70
+ }
71
+ };
72
+
41
73
  const set = (c) => {
42
74
  if (props.disabled) return;
43
75
  current.value = c;
@@ -57,19 +89,49 @@ watch(
57
89
  }
58
90
 
59
91
  .small-dropdown {
92
+ @apply h-10 !important;
60
93
  background-clip: border-box !important;
61
94
  /* border-radius: 100% !important; */
62
95
  padding: 0;
63
96
  width: 3em !important;
64
- /* height: 3em !important; */
65
97
  display: flex;
66
98
  justify-items: center;
67
99
  justify-content: center;
68
100
  }
69
101
 
70
- .dropdown-content {
71
- left: -1.1rem;
72
- z-index: 99999999999999;
102
+ @media (min-width: 768px) {
103
+ .small-dropdown {
104
+ height: 40px !important; /* Match input-default responsive height */
105
+ }
106
+ }
107
+
108
+ .dropdown-content-portal {
109
+ @apply bg-dark-400 border border-dark-650 rounded-lg shadow-2xl z-50;
110
+ padding: 0.5rem;
111
+ max-height: 192px !important;
112
+ overflow-y: auto !important;
113
+ overscroll-behavior: contain !important;
114
+ touch-action: pan-y !important;
115
+ -webkit-overflow-scrolling: touch !important;
116
+ scrollbar-width: none;
117
+ -ms-overflow-style: none;
118
+ }
119
+
120
+ .dropdown-content-portal::-webkit-scrollbar {
121
+ display: none;
122
+ }
123
+
124
+ .dropdown-content-portal > div {
125
+ @apply px-3 py-2 text-sm text-white cursor-pointer;
126
+ border-radius: 6px;
127
+ }
128
+
129
+ .dropdown-content-portal > div:hover {
130
+ /* Removed hover background */
131
+ }
132
+
133
+ .dropdown-content-portal > div .flex {
134
+ @apply items-center justify-center;
135
+ gap: 0.75rem;
73
136
  }
74
137
  </style>
75
- , watch
@@ -1,37 +1,36 @@
1
1
  <template>
2
2
  <Table>
3
- <Header class="text-center grid-cols-7 lg:grid-cols-8">
4
- <div class="col-span-3 lg:col-span-2 flex">
3
+ <Header class="grid-cols-7 text-center lg:grid-cols-8">
4
+ <div class="col-span-3 flex lg:col-span-2">
5
5
  <Checkbox
6
6
  class="mr-3"
7
7
  :toggled="ui.mainCheckbox.profiles"
8
8
  @valueUpdate="ui.toggleMainCheckbox('profiles')"
9
- />
9
+ :isHeader="true" />
10
10
  <div class="mx-auto flex items-center" @click="ui.toggleSort('eventId')">
11
- <img src="/img/profile.svg" width="14" class="mr-0 ipadlg:mr-3" />
12
- <h4 class="hidden ipadlg:flex">Profile Name</h4>
13
- <!-- <DownIcon v-if="ui.sortData.sortBy === 'eventId'" class="ml-1" /> -->
11
+ <ProfileIcon class="mr-0 h-4 w-4 md:mr-3" />
12
+ <h4 class="hidden md:flex">Profile Name</h4>
14
13
  </div>
15
14
  </div>
16
- <div class="lg:col-span-2 col-span-1 items-center justify-center flex" v-once>
17
- <CartIcon class="mr-0 ipadlg:mr-3" />
18
- <h4 class="hidden ipadlg:flex">Card Number</h4>
15
+ <div class="col-span-1 flex items-center justify-center lg:col-span-2" v-once>
16
+ <CartIcon class="mr-0 h-4 w-4 md:mr-3" />
17
+ <h4 class="hidden md:flex">Card Number</h4>
19
18
  </div>
20
19
  <div class="col-span-1 flex items-center justify-center" v-once>
21
- <TimerIcon class="mr-0 ipadlg:mr-3" />
22
- <h4 class="hidden ipadlg:flex">Exp. Date</h4>
20
+ <TimerIcon class="mr-0 h-4 w-4 md:mr-3" />
21
+ <h4 class="hidden md:flex">Expiration</h4>
23
22
  </div>
24
23
  <div class="col-span-1 flex items-center justify-center" v-once>
25
- <img src="/img/controls/enable.svg" width="14" class="mr-0 ipadlg:mr-3" />
26
- <h4 class="hidden ipadlg:flex">Enabled</h4>
24
+ <CheckmarkIcon class="mr-0 h-4 w-4 md:mr-3" />
25
+ <h4 class="hidden md:flex">Enabled</h4>
27
26
  </div>
28
- <div class="col-span-1 hidden lg:flex items-center justify-center" v-once>
29
- <TicketIcon class="mr-0 lg:mr-3" />
27
+ <div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
28
+ <TicketIcon class="mr-0 h-4 w-4 lg:mr-3" />
30
29
  <h4 class="hidden lg:flex">Tags</h4>
31
30
  </div>
32
31
  <div class="col-span-1 flex items-center justify-center" v-once>
33
- <ClickIcon class="mr-0 ipadlg:mr-3" />
34
- <h4 class="hidden ipadlg:flex">Actions</h4>
32
+ <ClickIcon class="mr-0 h-4 w-4 md:mr-3" />
33
+ <h4 class="hidden md:flex">Actions</h4>
35
34
  </div>
36
35
  </Header>
37
36
  <div v-if="toRender.length != 0">
@@ -39,26 +38,27 @@
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-2 divide-border overflow-y-auto hidden-scrollbars overflow-x-hidden stop-pan"
43
- >
41
+ class="scroller vue-recycle-scroller ready direction-vertical hidden-scrollbars stop-pan flex flex-col divide-y divide-dark-650 overflow-y-auto overflow-x-hidden"
42
+ :style="{ maxHeight: dynamicTableHeight }">
44
43
  <template #default="props">
45
- <div class="task" :key="i[props.item.index]">
44
+ <div class="profile" :key="`profile-${props.item.id || props.item.index}`">
46
45
  <Profile
47
46
  @click="i[props.item.index]++"
48
- :class="[props.item.index % 2 == 1 ? 'bg-dark-500' : 'bg-dark-550']"
49
- :task="props.item"
50
- />
47
+ :class="props.item.index % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
48
+ :profile="props.item" />
51
49
  </div>
52
50
  </template>
53
51
  </RecycleScroller>
54
52
  </div>
55
- <div v-else class="flex justify-center text-light-400 py-2 bg-dark-500 border-b-2 border-border">
56
- No profiles found
53
+ <div v-else class="empty-state flex flex-col items-center justify-center bg-dark-400 py-8 text-center">
54
+ <ProfileIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
55
+ <p class="text-sm text-light-400">No profiles found</p>
56
+ <p class="mt-1 text-xs text-light-500">Create profiles to get started</p>
57
57
  </div>
58
58
  </Table>
59
59
  </template>
60
60
  <style lang="scss" scoped>
61
- .task {
61
+ .profile {
62
62
  height: 64px;
63
63
  }
64
64
  h4 {
@@ -73,25 +73,82 @@ h4 {
73
73
  max-height: calc(100vh - 20rem);
74
74
  overflow: hidden;
75
75
  }
76
+
77
+ .empty-state {
78
+ color: #969696;
79
+ font-size: 14px;
80
+ font-weight: 500;
81
+ }
76
82
  </style>
77
83
  <script setup>
78
84
  import { Table, Header } from "@/components/Table";
79
- import { CartIcon, TicketIcon, ClickIcon, TimerIcon } from "@/components/icons";
85
+ import { CartIcon, TicketIcon, ClickIcon, TimerIcon, ProfileIcon, CheckmarkIcon } from "@/components/icons";
80
86
  import Profile from "./Profile.vue";
81
87
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
82
88
  import { useUIStore } from "@/stores/ui";
83
- import { computed, ref } from "vue";
89
+ import { computed, ref, onMounted, onUnmounted } from "vue";
84
90
 
85
91
  const props = defineProps({
86
- tasks: { type: Object }
92
+ profiles: { type: Object }
87
93
  });
88
- const i = ref({});
89
- props.tasks.forEach((t) => (i.value[t._id] = 0));
90
94
 
91
95
  const ui = useUIStore();
92
96
 
97
+ const i = ref({});
93
98
  const toRender = computed(() => {
94
99
  let c = 0;
95
- return props.tasks.map((t) => ({ ...t, index: c++ }));
100
+ const rendered = props.profiles.map((t) => ({ ...t, index: c++ }));
101
+
102
+ // Initialize reactive refs for click tracking
103
+ rendered.forEach((t) => {
104
+ if (t.id && !(t.id in i.value)) {
105
+ i.value[t.id] = 0;
106
+ }
107
+ if (!(t.index in i.value)) {
108
+ i.value[t.index] = 0;
109
+ }
110
+ });
111
+
112
+ return rendered;
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";
96
153
  });
97
154
  </script>
@@ -1,16 +1,77 @@
1
1
  <template>
2
- <div class="flex rounded-2xl w-fit shadow-3xl items-center justify-center bg-dark-600">
3
- <span class="font-bold p-2 truncate small">{{ props.text }}</span>
2
+ <div class="tag-pill">
3
+ <span class="tag-text">{{ formatText(props.text) }}</span>
4
4
  </div>
5
5
  </template>
6
6
 
7
- <style scoped>
8
- .small {
9
- font-size: 0.6rem;
10
- line-height: 0.8rem;
7
+ <style lang="scss" scoped>
8
+ .tag-pill {
9
+ @apply inline-flex items-center justify-center rounded-md transition-all duration-200;
10
+ background: linear-gradient(145deg, oklch(0.26 0 0), oklch(0.22 0 0));
11
+ border: 1px solid oklch(0.31 0 0);
12
+ padding: 0.1875rem 0.5rem;
13
+ min-width: 2rem;
14
+ max-width: 4.5rem;
15
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
16
+
17
+ &:hover {
18
+ background: linear-gradient(145deg, oklch(0.28 0 0), oklch(0.26 0 0));
19
+ border-color: oklch(0.33 0 0);
20
+ transform: translateY(-0.5px);
21
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
22
+ }
23
+ }
24
+
25
+ .tag-text {
26
+ @apply text-white font-semibold truncate;
27
+ font-size: 0.6875rem;
28
+ line-height: 1.1;
29
+ letter-spacing: 0.025em;
30
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4);
31
+ }
32
+
33
+ // Ultra responsive design
34
+ @media (max-width: 1024px) {
35
+ .tag-pill {
36
+ padding: 0.1rem 0.3rem;
37
+ max-width: 3.5rem;
38
+ }
39
+
40
+ .tag-text {
41
+ font-size: 0.575rem;
42
+ }
43
+ }
44
+
45
+ @media (max-width: 768px) {
46
+ .tag-pill {
47
+ padding: 0.075rem 0.25rem;
48
+ max-width: 3rem;
49
+ min-width: 1.25rem;
50
+ }
51
+
52
+ .tag-text {
53
+ font-size: 0.55rem;
54
+ }
55
+ }
56
+
57
+ @media (max-width: 480px) {
58
+ .tag-pill {
59
+ padding: 0.05rem 0.2rem;
60
+ max-width: 2.5rem;
61
+ min-width: 1rem;
62
+ }
63
+
64
+ .tag-text {
65
+ font-size: 0.5rem;
66
+ }
11
67
  }
12
68
  </style>
13
69
 
14
70
  <script setup>
15
71
  const props = defineProps({ text: { type: String } });
72
+
73
+ const formatText = (text) => {
74
+ if (!text) return "";
75
+ return text.toUpperCase();
76
+ };
16
77
  </script>