@necrolab/dashboard 0.5.15 → 0.5.17

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 (137) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +70 -566
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +61 -74
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +13 -21
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +295 -0
  20. package/src/assets/css/main.scss +55 -139
  21. package/src/components/Auth/LoginForm.vue +7 -86
  22. package/src/components/Console/ConsoleToolbar.vue +123 -0
  23. package/src/components/Editors/Account/Account.vue +12 -12
  24. package/src/components/Editors/Account/AccountView.vue +38 -111
  25. package/src/components/Editors/Account/CreateAccount.vue +11 -61
  26. package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
  27. package/src/components/Editors/AdminFileEditor.vue +179 -0
  28. package/src/components/Editors/Profile/CreateProfile.vue +77 -150
  29. package/src/components/Editors/Profile/Profile.vue +20 -21
  30. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  31. package/src/components/Editors/Profile/ProfileView.vue +41 -116
  32. package/src/components/Editors/ProxyFileEditor.vue +86 -0
  33. package/src/components/Editors/TagLabel.vue +16 -55
  34. package/src/components/Editors/TagToggle.vue +20 -8
  35. package/src/components/Filter/Filter.vue +66 -79
  36. package/src/components/Filter/FilterPreview.vue +153 -135
  37. package/src/components/Filter/PriceSortToggle.vue +36 -43
  38. package/src/components/Table/Header.vue +1 -1
  39. package/src/components/Table/Table.vue +45 -51
  40. package/src/components/Tasks/CheckStock.vue +7 -16
  41. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  42. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  43. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  44. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  45. package/src/components/Tasks/EventDetailRow.vue +21 -0
  46. package/src/components/Tasks/MassEdit.vue +6 -16
  47. package/src/components/Tasks/QuickSettings.vue +140 -216
  48. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  49. package/src/components/Tasks/Stats.vue +20 -39
  50. package/src/components/Tasks/Task.vue +64 -270
  51. package/src/components/Tasks/TaskLabel.vue +9 -3
  52. package/src/components/Tasks/TaskView.vue +45 -64
  53. package/src/components/Tasks/Utilities.vue +10 -44
  54. package/src/components/Tasks/ViewTask.vue +23 -107
  55. package/src/components/icons/Close.vue +2 -8
  56. package/src/components/icons/Gear.vue +8 -8
  57. package/src/components/icons/Hash.vue +5 -0
  58. package/src/components/icons/Key.vue +2 -8
  59. package/src/components/icons/Pencil.vue +2 -8
  60. package/src/components/icons/Profile.vue +2 -8
  61. package/src/components/icons/Sell.vue +2 -8
  62. package/src/components/icons/Spinner.vue +4 -7
  63. package/src/components/icons/Wildcard.vue +2 -8
  64. package/src/components/icons/index.js +3 -5
  65. package/src/components/ui/ActionButtonGroup.vue +113 -52
  66. package/src/components/ui/BalanceIndicator.vue +60 -0
  67. package/src/components/ui/EmptyState.vue +24 -0
  68. package/src/components/ui/EnableDisableToggle.vue +23 -0
  69. package/src/components/ui/FormField.vue +49 -49
  70. package/src/components/ui/IconLabel.vue +23 -0
  71. package/src/components/ui/InfoRow.vue +21 -54
  72. package/src/components/ui/Modal.vue +161 -54
  73. package/src/components/ui/Navbar.vue +63 -44
  74. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  75. package/src/components/ui/ReconnectIndicator.vue +111 -124
  76. package/src/components/ui/SectionCard.vue +6 -14
  77. package/src/components/ui/Splash.vue +2 -10
  78. package/src/components/ui/StatusBadge.vue +26 -28
  79. package/src/components/ui/TaskToggle.vue +54 -0
  80. package/src/components/ui/controls/CountryChooser.vue +29 -66
  81. package/src/components/ui/controls/EyeToggle.vue +1 -1
  82. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  83. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  84. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
  85. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  86. package/src/composables/useCodeEditor.js +117 -0
  87. package/src/composables/useColorMapping.js +15 -0
  88. package/src/composables/useCopyToClipboard.js +1 -1
  89. package/src/composables/useDateFormatting.js +21 -0
  90. package/src/composables/useDeviceDetection.js +14 -0
  91. package/src/composables/useDropdownPosition.js +1 -4
  92. package/src/composables/useDynamicTableHeight.js +31 -0
  93. package/src/composables/useEnableDisable.js +6 -0
  94. package/src/composables/useFilterCSS.js +71 -0
  95. package/src/composables/useFormValidation.js +92 -0
  96. package/src/composables/useGetAllTags.js +9 -0
  97. package/src/composables/useIOSViewportHandling.js +76 -0
  98. package/src/composables/useNotchHandling.js +306 -0
  99. package/src/composables/useRowSelection.js +0 -3
  100. package/src/composables/useTableRender.js +23 -0
  101. package/src/composables/useTicketPricing.js +16 -0
  102. package/src/composables/useWindowDimensions.js +21 -0
  103. package/src/composables/useZoomPrevention.js +96 -0
  104. package/src/constants/tableLayout.js +14 -0
  105. package/src/libs/Filter.js +14 -20
  106. package/src/libs/panzoom.js +1 -5
  107. package/src/libs/utils/array.js +58 -0
  108. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  109. package/src/libs/utils/eventUrl.js +40 -0
  110. package/src/libs/utils/string.js +3 -0
  111. package/src/libs/utils/time.js +20 -0
  112. package/src/libs/utils/validation.js +64 -0
  113. package/src/main.js +0 -2
  114. package/src/stores/connection.js +1 -29
  115. package/src/stores/logger.js +6 -12
  116. package/src/stores/sampleData.js +1 -2
  117. package/src/stores/ui.js +80 -71
  118. package/src/utils/tableHelpers.js +1 -0
  119. package/src/views/Accounts.vue +19 -38
  120. package/src/views/Console.vue +74 -253
  121. package/src/views/Editor.vue +47 -1114
  122. package/src/views/FilterBuilder.vue +190 -461
  123. package/src/views/Login.vue +3 -28
  124. package/src/views/Profiles.vue +17 -32
  125. package/src/views/Tasks.vue +51 -38
  126. package/tailwind.config.js +82 -71
  127. package/workbox-config.cjs +47 -5
  128. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  129. package/exit +0 -209
  130. package/run +0 -177
  131. package/src/assets/css/base/color-fallbacks.scss +0 -10
  132. package/src/assets/img/background.svg.backup +0 -11
  133. package/src/components/icons/SquareCheck.vue +0 -18
  134. package/src/components/icons/SquareUncheck.vue +0 -18
  135. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  136. package/switch-branch.sh +0 -41
  137. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -1,9 +1,17 @@
1
1
  <template>
2
- <div class="modal-mask fixed inset-0 z-modal bg-overlay-dark backdrop-blur-xs flex scrollable overflow-y-auto" role="dialog" @touchmove.stop>
3
- <div class="component-modal" ref="target">
2
+ <div
3
+ class="modal-mask"
4
+ :class="{ 'fade-in': !isClosing, 'fade-out': isClosing }"
5
+ role="dialog"
6
+ aria-modal="true"
7
+ @touchmove.stop>
8
+ <div
9
+ class="modal-content"
10
+ :class="{ 'modal-slide-in': !isClosing, 'modal-slide-out': isClosing }"
11
+ ref="target">
4
12
  <div class="modal-header">
5
13
  <slot name="header" />
6
- <button @click="ui.toggleModal()" class="btn-icon border-none hover:bg-dark-400">
14
+ <button @click="closeModal" class="btn-icon border-none hover:bg-dark-400 ml-auto" aria-label="Close modal">
7
15
  <CloseIcon />
8
16
  </button>
9
17
  </div>
@@ -21,106 +29,205 @@ import { CloseIcon } from "@/components/icons";
21
29
 
22
30
  const ui = useUIStore();
23
31
  const target = ref(null);
32
+ const isClosing = ref(false);
33
+
34
+ const closeModal = () => {
35
+ isClosing.value = true;
36
+ setTimeout(() => {
37
+ ui.toggleModal();
38
+ isClosing.value = false; // Reset for next modal
39
+ }, 150); // Match the animation duration
40
+ };
24
41
 
25
42
  // Store original body styles
26
43
  let originalOverflow = "";
44
+ let originalPosition = "";
45
+ let originalTop = "";
46
+ let scrollY = 0;
27
47
 
28
48
  onMounted(() => {
29
- // Lock body scroll - simple overflow approach
49
+ isClosing.value = false; // Ensure clean state on mount
50
+ scrollY = window.scrollY;
30
51
  originalOverflow = document.body.style.overflow;
52
+ originalPosition = document.body.style.position;
53
+ originalTop = document.body.style.top;
54
+
31
55
  document.body.style.overflow = "hidden";
56
+ document.body.style.position = "fixed";
57
+ document.body.style.top = `-${scrollY}px`;
58
+ document.body.style.width = "100%";
32
59
  });
33
60
 
34
61
  onUnmounted(() => {
35
- // Restore body scroll
36
- document.body.style.overflow = originalOverflow || "";
62
+ document.body.style.overflow = originalOverflow;
63
+ document.body.style.position = originalPosition;
64
+ document.body.style.top = originalTop;
65
+ document.body.style.width = "";
66
+ window.scrollTo(0, scrollY);
37
67
  });
38
68
 
39
69
  onClickOutside(target, (event) => {
40
- if (event.target.classList.contains("modal-mask")) ui.toggleModal();
70
+ if (event.target.classList.contains("modal-mask")) closeModal();
41
71
  });
42
72
  </script>
43
- <style lang="scss" scoped>
73
+ <style scoped>
44
74
  .modal-mask {
45
- @apply w-screen duration-300 ease-in-out;
46
- align-items: flex-start;
47
- justify-content: center;
48
- padding: 2rem 1rem 4rem 1rem !important; /* More bottom padding for scrollable space */
75
+ @apply fixed left-0 top-0;
76
+ @apply flex w-screen overflow-y-auto;
77
+ @apply items-start justify-center;
78
+ @apply p-4 pt-14;
79
+ @apply duration-300 ease-in-out;
80
+ @apply z-modal-mask;
81
+
82
+ /* Background with opacity */
83
+ background-color: oklch(0 0 0 / 0.85);
84
+ backdrop-filter: blur(8px);
85
+
86
+ /* Height with modern viewport units and fallback */
49
87
  height: 100dvh;
50
- overflow-y: auto;
88
+
89
+ /* Touch handling for mobile */
51
90
  -webkit-overflow-scrolling: touch;
52
91
  touch-action: pan-y !important;
92
+ }
53
93
 
54
- @supports not (height: 100dvh) {
55
- height: 100vh;
94
+ @supports (height: 100dvh) {
95
+ .modal-mask {
96
+ height: 100dvh;
56
97
  }
57
98
  }
58
99
 
59
- .component-modal {
60
- width: 640px;
61
- overflow-y: visible;
62
- @apply flex flex-col rounded-lg bg-dark-300 px-5 py-5;
63
-
64
- .modal-header {
65
- @apply flex font-bold text-white;
66
-
67
- button {
68
- @apply ml-auto;
69
- }
100
+ @supports not (height: 100dvh) {
101
+ .modal-mask {
102
+ height: 100vh;
70
103
  }
104
+ }
71
105
 
72
- .modal-body {
73
- @apply flex flex-col pb-6 md:pb-4;
74
- flex: 1;
106
+ /* Tablet breakpoint */
107
+ @media (max-width: 810px) {
108
+ .modal-mask {
109
+ @apply items-start justify-center;
110
+ @apply p-4 pt-12;
111
+ padding-bottom: 15rem !important;
75
112
  }
76
113
  }
77
114
 
78
- /* Desktop - extra bottom padding for scrollable space */
79
- @media (min-width: 811px) {
115
+ /* Mobile portrait breakpoint */
116
+ @media (max-width: 480px) and (orientation: portrait) {
80
117
  .modal-mask {
81
- padding: 2rem 1rem 6rem 1rem !important;
118
+ padding-bottom: 3rem !important;
82
119
  }
83
120
  }
84
121
 
85
- @media (max-width: 810px) {
86
- .modal-mask {
87
- padding: 3rem 1rem !important; /* Mobile: more padding */
122
+ .modal-content {
123
+ @apply flex flex-col rounded-lg;
124
+ @apply bg-dark-400 px-5 py-5;
125
+ @apply w-160 mb-80;
126
+ @apply overflow-y-visible;
127
+ box-shadow: 0 25px 50px -12px oklch(0 0 0 / 0.8),
128
+ 0 0 0 1px oklch(1 0 0 / 0.05);
129
+ }
130
+
131
+ /* Mobile breakpoint for modal-content */
132
+ @media (max-width: 480px) {
133
+ .modal-content {
134
+ margin-bottom: 25rem;
88
135
  }
136
+ }
89
137
 
90
- .component-modal {
138
+ /* Tablet breakpoint for modal-content */
139
+ @media (max-width: 810px) {
140
+ .modal-content {
91
141
  width: calc(100vw - 2rem);
142
+ margin-bottom: 10rem;
143
+ }
144
+ }
145
+
146
+ /* Mobile portrait overrides */
147
+ @media (max-width: 480px) and (orientation: portrait) {
148
+ .modal-content {
149
+ margin-bottom: 3rem !important;
150
+ max-height: none !important;
92
151
  }
152
+ }
93
153
 
154
+ .modal-header {
155
+ @apply flex font-bold text-white;
156
+ }
157
+
158
+ .modal-body {
159
+ @apply flex flex-col flex-1;
160
+ }
161
+
162
+ /* Tablet breakpoint for modal-body */
163
+ @media (max-width: 810px) {
94
164
  .modal-body {
95
- overflow-y: visible;
96
- flex: 1;
97
- min-height: 0;
165
+ @apply pb-16 overflow-y-visible min-h-0;
98
166
  }
99
167
  }
100
168
 
101
- /* iPhone portrait mode - extra bottom spacing */
169
+ /* Mobile portrait breakpoint for modal-body */
102
170
  @media (max-width: 480px) and (orientation: portrait) {
103
- .modal-mask {
104
- padding-bottom: 4rem !important;
171
+ .modal-body {
172
+ padding-bottom: 1rem !important;
105
173
  }
174
+ }
175
+
176
+ /* Modal animations */
177
+ .fade-in {
178
+ animation: fadeIn 0.15s ease-out;
179
+ }
106
180
 
107
- .component-modal {
108
- max-height: none;
181
+ .modal-slide-in {
182
+ animation: slideIn 0.15s ease-out;
183
+ }
184
+
185
+ @keyframes fadeIn {
186
+ from {
187
+ opacity: 0;
188
+ }
189
+ to {
190
+ opacity: 1;
109
191
  }
110
192
  }
111
193
 
112
- /* PWA mode - extra spacing for iPhone status bar and home indicator */
113
- @media (display-mode: standalone) {
114
- .modal-mask {
115
- padding-top: 3rem !important;
116
- padding-bottom: 6rem !important;
194
+ @keyframes slideIn {
195
+ from {
196
+ opacity: 0;
197
+ transform: translateY(-20px) scale(0.95);
198
+ }
199
+ to {
200
+ opacity: 1;
201
+ transform: translateY(0) scale(1);
202
+ }
203
+ }
204
+
205
+ /* Exit animations */
206
+ .fade-out {
207
+ animation: fadeOut 0.15s ease-in forwards;
208
+ }
209
+
210
+ .modal-slide-out {
211
+ animation: slideOut 0.15s ease-in forwards;
212
+ }
213
+
214
+ @keyframes fadeOut {
215
+ from {
216
+ opacity: 1;
117
217
  }
218
+ to {
219
+ opacity: 0;
220
+ }
221
+ }
118
222
 
119
- @media (max-width: 810px) {
120
- .modal-mask {
121
- padding-top: 4rem !important;
122
- padding-bottom: 5rem !important;
123
- }
223
+ @keyframes slideOut {
224
+ from {
225
+ opacity: 1;
226
+ transform: translateY(0) scale(1);
227
+ }
228
+ to {
229
+ opacity: 0;
230
+ transform: translateY(-10px) scale(0.98);
124
231
  }
125
232
  }
126
233
  </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
- <div class="navbar" :class="{ 'z-max': menuOpen }">
3
- <div :class="['component-container ios-wrapper flex items-center relative px-2 sm:px-3 lg:px-4', { 'ios-wrapper': landscapeIos }]">
2
+ <div class="navbar" :class="{ 'force-z': menuOpen }">
3
+ <div :class="['component-container ios-wrapper flex items-center relative px-3 lg:px-4', { 'ios-wrapper': landscapeIos }]">
4
4
  <router-link to="/">
5
5
  <img src="@/assets/img/logo_trans.png" class="h-6 lg:h-8 mr-4 z-30 object-cover cursor-pointer" alt="Logo: Necro" />
6
6
  </router-link>
@@ -31,7 +31,7 @@
31
31
  </li>
32
32
  </ul>
33
33
 
34
- <button class="hidden lg:block ml-auto mr-4 smooth-hover" @click="logout()">
34
+ <button class="hidden lg:block ml-auto mr-4 smooth-hover" @click="logout()" aria-label="Logout">
35
35
  <LogoutIcon />
36
36
  </button>
37
37
  <h4 v-if="ui.profile?.name" class="hidden lg:block text-white text-sm font-medium">
@@ -45,12 +45,12 @@
45
45
  v-if="ui.profile?.profilePicture"
46
46
  :src="ui.profile?.profilePicture"
47
47
  alt="Profile Picture"
48
- class="h-10 w-10 rounded-full hidden lg:block mx-4"
48
+ class="size-10 rounded-full hidden lg:block mx-4"
49
49
  />
50
- <div v-else class="h-10 w-10 rounded-full hidden lg:block mx-4 bg-dark-400" />
50
+ <div v-else class="size-10 rounded-full hidden lg:block mx-4 bg-dark-400" />
51
51
  <CountryChooser class="hidden lg:block" />
52
52
 
53
- <button class="flex lg:hidden ml-auto z-30" @click="toggleMenu">
53
+ <button class="flex lg:hidden ml-auto z-30" @click="toggleMenu" aria-label="Toggle navigation menu" :aria-expanded="menuOpen">
54
54
  <MenuIcon />
55
55
  </button>
56
56
  </div>
@@ -85,14 +85,14 @@
85
85
  </ul>
86
86
  <CountryChooser class="mx-auto block landscape:hidden mb-auto" />
87
87
  <div class="flex mx-auto items-center landscape:mb-0">
88
- <button class="mr-4" @click="logout()">
88
+ <button class="mr-4" @click="logout()" aria-label="Logout">
89
89
  <LogoutIcon />
90
90
  </button>
91
91
  <h4 class="text-white text-sm font-medium mr-4">
92
92
  <span class="text-lightgray">Logged in as </span>
93
93
  <span class="font-black"> {{ ui.profile?.name }}</span>
94
94
  </h4>
95
- <img :src="ui.profile?.profilePicture" alt="" class="h-10 w-10 rounded-full" />
95
+ <img :src="ui.profile?.profilePicture" alt="" class="size-10 rounded-full" />
96
96
  </div>
97
97
 
98
98
  <div class="mx-auto mt-6 mb-14 flex gap-3 items-center">
@@ -111,15 +111,19 @@
111
111
  </template>
112
112
  <style lang="scss" scoped>
113
113
  .navbar {
114
- @apply border-b py-5 fixed w-full bg-dark-300/95 backdrop-blur border-dark-600;
114
+ @apply border-b py-5 fixed w-full;
115
115
  top: 0;
116
116
  left: 0;
117
117
  z-index: 1000;
118
+ background: theme('colors.dark.300 / 0.95');
119
+ backdrop-filter: blur(23px);
120
+ -webkit-backdrop-filter: blur(23px);
121
+ border-color: theme('colors.border');
118
122
 
119
123
  // Consistent padding base
120
124
  padding-top: 1.25rem;
121
125
  padding-bottom: 1.25rem;
122
-
126
+
123
127
  // Only add safe area top padding for portrait notch devices
124
128
  @supports (padding-top: env(safe-area-inset-top)) {
125
129
  @media (max-device-width: 430px) and (orientation: portrait) {
@@ -135,7 +139,7 @@
135
139
  left: 0;
136
140
  right: 0;
137
141
  height: 100px;
138
- background: oklch(0.1822 0 0);
142
+ background: theme('colors.dark.300');
139
143
  backdrop-filter: blur(8px);
140
144
  -webkit-backdrop-filter: blur(8px);
141
145
  z-index: -1;
@@ -144,27 +148,38 @@
144
148
  ul {
145
149
  @apply gap-x-4;
146
150
 
151
+ // Global SVG normalization - force all icons to be identical
152
+ svg {
153
+ width: 20px !important;
154
+ height: 20px !important;
155
+ display: block !important;
156
+ flex-shrink: 0 !important;
157
+ box-sizing: border-box !important;
158
+ }
159
+
147
160
  li a {
148
- @apply flex text-white text-sm items-center rounded-lg h-10 border-b-2 border-transparent;
161
+ @apply flex text-white text-sm items-center rounded-lg;
162
+ height: 40px;
163
+ border: 1px solid transparent;
149
164
  border-left: 3px solid transparent;
150
165
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
151
166
  position: relative;
152
167
 
153
168
  svg {
154
- @apply mr-2 w-5 h-5;
169
+ @apply mr-2;
155
170
  }
156
171
 
157
172
  &.router-link-exact-active {
158
- border-bottom: 2px solid oklch(0.72 0.15 145);
159
- color: oklch(0.72 0.15 145);
160
- background: oklch(0.72 0.15 145 / 0.08);
173
+ border-bottom: 2px solid theme('colors.primary');
174
+ color: theme('colors.primary');
175
+ background: theme('colors.primary / 0.08');
161
176
  margin-bottom: -2px;
162
177
 
163
178
  // Mobile styling
164
179
  @media (max-width: 1023px) {
165
- border: 2px solid oklch(0.72 0.15 145);
166
- color: oklch(0.72 0.15 145);
167
- background: oklch(0.72 0.15 145 / 0.08);
180
+ border: 2px solid theme('colors.primary');
181
+ color: theme('colors.primary');
182
+ background: theme('colors.primary / 0.08');
168
183
  padding: 0.5rem 0.75rem;
169
184
  border-radius: 0.5rem;
170
185
  margin-bottom: 0;
@@ -175,26 +190,35 @@
175
190
  // Desktop mode (lg to xl): icon-only view with perfect centering
176
191
  @media (min-width: 1024px) and (max-width: 1279px) {
177
192
  li a {
178
- @apply w-10 h-10 flex items-center justify-center;
179
- padding: 0;
193
+ width: 40px;
194
+ height: 40px;
195
+ padding: 0 !important;
196
+ display: flex !important;
197
+ align-items: center !important;
198
+ justify-content: center !important;
180
199
  position: relative;
181
200
 
182
201
  // Hide text completely in icon-only mode
183
202
  span {
184
- display: none;
203
+ display: none !important;
185
204
  }
186
205
 
187
206
  svg {
188
- @apply m-0 absolute top-1/2 left-1/2;
207
+ margin: 0 !important;
208
+ padding: 0 !important;
209
+ position: absolute;
210
+ top: 50%;
211
+ left: 50%;
189
212
  transform: translate(-50%, -50%);
190
- padding: 0;
191
213
  }
192
214
 
193
215
  &.router-link-exact-active {
194
- @apply border-2 w-10 h-10 rounded-lg;
195
- border-color: oklch(0.72 0.15 145);
196
- color: oklch(0.72 0.15 145);
197
- background: oklch(0.72 0.15 145 / 0.08);
216
+ border: 2px solid theme('colors.primary') !important;
217
+ color: theme('colors.primary') !important;
218
+ background: theme('colors.primary / 0.08') !important;
219
+ border-radius: 0.5rem !important;
220
+ width: 40px !important;
221
+ height: 40px !important;
198
222
  }
199
223
  }
200
224
  }
@@ -202,7 +226,8 @@
202
226
  // XL and above: full width with text
203
227
  @media (min-width: 1280px) {
204
228
  li a {
205
- @apply px-4 h-10;
229
+ @apply px-4;
230
+ height: 40px;
206
231
 
207
232
  svg {
208
233
  @apply mr-2;
@@ -212,6 +237,9 @@
212
237
  }
213
238
  }
214
239
 
240
+ .force-z {
241
+ z-index: 20000;
242
+ }
215
243
 
216
244
  .mobile-menu {
217
245
  margin-top: 0;
@@ -231,17 +259,17 @@
231
259
 
232
260
  .version-badge {
233
261
  @apply flex items-center gap-x-2 px-3 py-2 rounded-lg border;
234
- background: oklch(0.2046 0 0);
235
- border-color: oklch(0.2809 0 0);
262
+ background: theme('colors.bg-elevated');
263
+ border-color: theme('colors.dark.550');
236
264
 
237
265
  .version-label {
238
266
  @apply text-xs font-medium;
239
- color: oklch(0.65 0 0);
267
+ color: theme('colors.text-muted');
240
268
  }
241
269
 
242
270
  .version-number {
243
271
  @apply text-xs font-bold;
244
- color: oklch(0.72 0.15 145);
272
+ color: theme('colors.primary');
245
273
  }
246
274
  }
247
275
  </style>
@@ -263,20 +291,11 @@ import { useUIStore } from "@/stores/ui";
263
291
  import router from "@/router/index";
264
292
  import CountryChooser from "@/components/ui/controls/CountryChooser.vue";
265
293
  import { sendLogout } from "@/stores/requests";
294
+ import { useDeviceDetection } from "@/composables/useDeviceDetection";
266
295
 
267
296
  const landscapeIos = ref(false);
268
297
 
269
- function isIOS() {
270
- if (/iPad|iPhone|iPod/.test(navigator.platform)) {
271
- return true;
272
- } else {
273
- return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
274
- }
275
- }
276
-
277
- function isIpadOS() {
278
- return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform);
279
- }
298
+ const { isIOS, isIpadOS } = useDeviceDetection();
280
299
 
281
300
  window.matchMedia("(orientation: portrait)").addEventListener("change", (e) => {
282
301
  if (!e.matches && isIOS() && !isIpadOS()) landscapeIos.value = true;
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div class="mt-6 grid grid-cols-12 gap-3 pt-4 border-t border-dark-550">
3
+ <div v-if="data.tags && data.tags.length > 0" class="col-span-6">
4
+ <label class="flex items-center mb-2 text-light-200 font-medium">Tags</label>
5
+ <div class="flex gap-2 flex-wrap">
6
+ <TagLabel v-for="tag in data.tags" :key="tag" :text="tag" />
7
+ </div>
8
+ </div>
9
+ <div class="col-span-6">
10
+ <label class="flex items-center mb-2 text-light-200 font-medium">Status</label>
11
+ <div class="flex-gap-3 items-center h-10">
12
+ <StatusBadge :enabled="data.enabled" size="large" />
13
+ <span class="text-sm font-medium" :class="data.enabled ? 'text-green-400' : 'text-red-400'">
14
+ {{ data.enabled ? 'Enabled' : 'Disabled' }}
15
+ </span>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </template>
20
+
21
+ <script setup>
22
+ import StatusBadge from "@/components/ui/StatusBadge.vue";
23
+ import TagLabel from "@/components/Editors/TagLabel.vue";
24
+
25
+ defineProps({
26
+ data: {
27
+ type: Object,
28
+ required: true
29
+ }
30
+ });
31
+ </script>