@necrolab/dashboard 0.4.38 → 0.4.39

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 (60) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/backend/api.js +20 -16
  3. package/backend/auth.js +2 -2
  4. package/backend/batching.js +1 -1
  5. package/backend/endpoints.js +5 -5
  6. package/backend/index.js +2 -2
  7. package/backend/mock-data.js +19 -20
  8. package/backend/mock-src/classes/logger.js +5 -7
  9. package/backend/mock-src/classes/utils.js +3 -2
  10. package/backend/mock-src/ticketmaster.js +2 -2
  11. package/backend/validator.js +2 -2
  12. package/dev-server.js +49 -0
  13. package/index.html +1 -1
  14. package/index.js +1 -1
  15. package/package.json +6 -5
  16. package/postcss.config.js +1 -1
  17. package/postinstall.js +5 -3
  18. package/public/android-chrome-192x192.png +0 -0
  19. package/public/android-chrome-512x512.png +0 -0
  20. package/public/apple-touch-icon.png +0 -0
  21. package/public/favicon-16x16.png +0 -0
  22. package/public/favicon-32x32.png +0 -0
  23. package/public/favicon.ico +0 -0
  24. package/public/manifest.json +4 -4
  25. package/src/App.vue +471 -49
  26. package/src/assets/css/_input.scss +25 -25
  27. package/src/assets/css/main.scss +116 -22
  28. package/src/assets/img/android-chrome-192x192.png +0 -0
  29. package/src/assets/img/logo_icon-old.png +0 -0
  30. package/src/assets/img/logo_icon.png +0 -0
  31. package/src/components/Editors/Account/Account.vue +19 -19
  32. package/src/components/Editors/Account/AccountCreator.vue +3 -3
  33. package/src/components/Editors/Account/AccountView.vue +17 -6
  34. package/src/components/Editors/Profile/Profile.vue +24 -24
  35. package/src/components/Editors/Profile/ProfileView.vue +22 -9
  36. package/src/components/Editors/TagLabel.vue +6 -7
  37. package/src/components/Table/Table.vue +15 -0
  38. package/src/components/Tasks/Controls/DesktopControls.vue +1 -1
  39. package/src/components/Tasks/CreateTaskAXS.vue +15 -15
  40. package/src/components/Tasks/CreateTaskTM.vue +5 -4
  41. package/src/components/Tasks/Task.vue +23 -35
  42. package/src/components/Tasks/TaskView.vue +19 -18
  43. package/src/components/Tasks/Utilities.vue +1 -1
  44. package/src/components/icons/Mail.vue +2 -2
  45. package/src/components/ui/Modal.vue +84 -15
  46. package/src/components/ui/Navbar.vue +118 -39
  47. package/src/components/ui/controls/atomic/Dropdown.vue +23 -3
  48. package/src/components/ui/controls/atomic/MultiDropdown.vue +43 -23
  49. package/src/stores/sampleData.js +5 -4
  50. package/src/stores/ui.js +3 -3
  51. package/src/views/Accounts.vue +2 -2
  52. package/src/views/Console.vue +78 -30
  53. package/src/views/Editor.vue +175 -24
  54. package/src/views/FilterBuilder.vue +21 -7
  55. package/src/views/Profiles.vue +8 -8
  56. package/src/views/Tasks.vue +51 -2
  57. package/tailwind.config.js +1 -1
  58. package/vite.config.js +8 -1
  59. package/vue.config.js +1 -1
  60. package/{workbox-config.js → workbox-config.cjs} +1 -4
@@ -22,28 +22,20 @@
22
22
  <div class="flex items-center gap-3">
23
23
  <!-- Hide Monitors and Auto buttons only on desktop -->
24
24
  <div class="hidden ipadlg:flex items-center gap-3">
25
- <button
26
- class="flex rounded gap-3 card-dark px-2 h-10 flex-center"
27
- >
25
+ <button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
28
26
  <h3 class="text-sm text-white">Hide Monitors</h3>
29
27
  <Switch class="scale-75" v-model="filteredLogs" />
30
28
  </button>
31
- <button
32
- class="flex rounded gap-3 card-dark px-2 h-10 flex-center"
33
- >
29
+ <button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
34
30
  <h3 class="text-sm text-white">Auto</h3>
35
31
  <Switch class="scale-75" v-model="autoscrollToggled" />
36
32
  </button>
37
33
  </div>
38
34
  <!-- Scroll buttons always visible -->
39
- <button
40
- class="rounded card-dark w-10 h-10 flex-center"
41
- >
35
+ <button class="rounded card-dark w-10 h-10 flex-center">
42
36
  <UpIcon class="cursor-pointer mx-1 lg:w-4 w-5 h-5" @click="scrollTo('up')" />
43
37
  </button>
44
- <button
45
- class="rounded card-dark w-10 h-10 flex-center"
46
- >
38
+ <button class="rounded card-dark w-10 h-10 flex-center">
47
39
  <DownIcon class="cursor-pointer ml-1 lg:w-4 w-5 h-5" @click="scrollTo('down')" />
48
40
  </button>
49
41
  </div>
@@ -51,28 +43,40 @@
51
43
 
52
44
  <Smoothie
53
45
  :weight="0.1"
54
- class="hidden-scrollbars console overflow-y-auto overflow-x-hidden font-mono text-white"
46
+ class="console overflow-y-auto overflow-x-hidden font-mono text-white scrollable"
55
47
  style="min-height: 14rem !important"
56
48
  ref="$autoscroll"
49
+ @wheel.stop
50
+ @touchmove.stop
57
51
  >
52
+ <div
53
+ v-if="
54
+ (currentTaskLog && currentTaskLog !== ''
55
+ ? taskLogMapping[currentTaskLog]
56
+ : logLines.filter((l) => (filteredLogs ? !['-DISCORD'].some((s) => l.includes(s)) : true))
57
+ ).length === 0
58
+ "
59
+ class="flex flex-col items-center justify-center h-full empty-state text-center"
60
+ >
61
+ <ConsoleIcon class="w-12 h-12 text-dark-400 mb-3 opacity-50" />
62
+ <p class="text-light-400 text-sm">No logs yet</p>
63
+ <p class="text-light-500 text-xs mt-1">Logs will appear here in real time</p>
64
+ </div>
58
65
  <pre
66
+ v-else
59
67
  class="hidden-scrollbars"
60
- v-for="line in currentTaskLog
68
+ v-for="(line, index) in currentTaskLog && currentTaskLog !== ''
61
69
  ? taskLogMapping[currentTaskLog]
62
70
  : logLines.filter((l) => (filteredLogs ? !['-DISCORD'].some((s) => l.includes(s)) : true))"
63
- v-bind:key="line"
71
+ v-bind:key="`log-${index}`"
64
72
  ><code class="md:text-sm lg:text-base" v-html="line"></code></pre>
65
73
  </Smoothie>
66
74
  <div class="flex ipadlg:hidden justify-between mt-3">
67
- <button
68
- class="flex rounded gap-3 card-dark px-2 h-10 flex-center"
69
- >
75
+ <button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
70
76
  <h3 class="text-sm text-white">Hide Monitors</h3>
71
77
  <Switch class="scale-75" v-model="filteredLogs" />
72
78
  </button>
73
- <button
74
- class="flex rounded gap-3 card-dark px-2 h-10 flex-center"
75
- >
79
+ <button class="flex rounded gap-3 card-dark px-2 h-10 flex-center">
76
80
  <h3 class="text-sm text-white">Auto</h3>
77
81
  <Switch class="scale-75" v-model="autoscrollToggled" />
78
82
  </button>
@@ -84,6 +88,39 @@
84
88
  .console {
85
89
  @apply bg-dark-400 lg:p-5 p-2 rounded relative border-dark-550 border-2;
86
90
  height: calc(100vh - 16.5rem);
91
+
92
+ // Enable scrollbars for console
93
+ scrollbar-width: thin;
94
+ scrollbar-color: #555 #2e2f34;
95
+
96
+ &::-webkit-scrollbar {
97
+ width: 8px;
98
+ }
99
+
100
+ &::-webkit-scrollbar-track {
101
+ background: #2e2f34;
102
+ }
103
+
104
+ &::-webkit-scrollbar-thumb {
105
+ background: #555;
106
+ border-radius: 4px;
107
+ }
108
+
109
+ &::-webkit-scrollbar-thumb:hover {
110
+ background: #777;
111
+ }
112
+
113
+ // Empty state styling
114
+ .empty-state {
115
+ min-height: 14rem;
116
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
117
+ }
118
+
119
+ // iPhone portrait - disable scroll since everything fits
120
+ @media (max-width: 480px) and (orientation: portrait) {
121
+ height: calc(100vh - 18rem);
122
+ overflow: hidden;
123
+ }
87
124
  textarea {
88
125
  background: transparent;
89
126
  resize: none;
@@ -97,11 +134,11 @@
97
134
  .console {
98
135
  height: calc(100vh - 20rem);
99
136
  @apply p-1 text-xs;
100
-
137
+
101
138
  pre {
102
139
  line-height: 1.2;
103
140
  }
104
-
141
+
105
142
  code {
106
143
  font-size: 0.7rem !important;
107
144
  }
@@ -151,13 +188,17 @@ const logLines = ref([]);
151
188
  const ansii = new Filter();
152
189
  const autoscrollToggled = ref(true);
153
190
  const taskLogMapping = ref({});
154
- const currentTaskLog = ref(false);
191
+ const currentTaskLog = ref("");
155
192
  const filteredLogs = ref(true);
156
193
 
157
194
  const path = "/api/updates?type=console";
158
195
  const url = (window.location.protocol === "http:" ? "ws://" : "wss://") + window.location.host + path;
159
196
  const scrollTo = (dir) => {
160
197
  try {
198
+ if (!$autoscroll.value?.el) {
199
+ if (DEBUG) console.log("Autoscroll element not ready");
200
+ return;
201
+ }
161
202
  if (dir === "up") $autoscroll.value.el.scrollTop = 0;
162
203
  else $autoscroll.value.el.scrollTop = parseInt($autoscroll.value.el.children[1].style.height);
163
204
  } catch (e) {
@@ -166,7 +207,7 @@ const scrollTo = (dir) => {
166
207
  };
167
208
 
168
209
  const addAnsiToOutput = (a) => {
169
- const html = ansii.toHtml(a.log);
210
+ const html = ansii.toHtml(a?.log || a);
170
211
  logLines.value.push(html);
171
212
  if (autoscrollToggled.value)
172
213
  nextTick().then(() => {
@@ -206,11 +247,18 @@ const makeTaskLogMapping = (lines) => {
206
247
 
207
248
  window.startDebugConsoleMessages = () => {
208
249
  setInterval(() => {
209
- const line = `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${Math.round(
210
- Math.random() * 10
211
- )}]\u001b[00m AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Needs To Queue: false - Queue up: false`;
212
- addAnsiToOutput(line);
213
- makeTaskLogMapping([line]);
250
+ const log = {
251
+ log: `\u001b[96m[12:16:02.273]\u001b[00m \u001b[96m[TASK-${Math.round(
252
+ Math.random() * 10
253
+ )}]\u001b[00m TEST TEST TEST Needs To Queue: false - Queue up: false`,
254
+ metadata: {
255
+ taskId: Math.round(Math.random() * 10),
256
+ siteId: "TM_US",
257
+ global: false
258
+ }
259
+ };
260
+ addAnsiToOutput(log);
261
+ makeTaskLogMapping([log]);
214
262
  }, 500);
215
263
  };
216
264
 
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <div>
3
3
  <!-- Heading -->
4
- <GearIcon class="w-5 cursor-pointer" @click="ui.toggleModal('quick-settings')" />
5
4
  <h4 class="text-white text-xl font-bold mb-5 pt-5 flex gap-2 items-center">
6
5
  Editor <img src="@/assets/img/pencil.svg" />
7
6
  </h4>
@@ -9,8 +8,8 @@
9
8
  <div class="card-dark p-2">
10
9
  <div v-if="ui.profile.admin">
11
10
  <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Admin Editor</h5>
12
- <div class="flex justify-between items-center">
13
- <div class="w-64 flex">
11
+ <div class="flex justify-between items-center gap-4 editor-controls-row">
12
+ <div class="w-64 flex flex-shrink-0 dropdown-container">
14
13
  <div class="relative flex-grow">
15
14
  <Dropdown
16
15
  class="bg-dark-500 border-2 border-r-0 border-dark-550 admin-file-dropdown w-full"
@@ -30,7 +29,7 @@
30
29
  </button>
31
30
  </div>
32
31
  <!-- Admin editor buttons moved here -->
33
- <div v-if="textEditorVisible" class="flex gap-x-2" style="margin-right: 1px;">
32
+ <div v-if="textEditorVisible" class="flex gap-x-2 flex-shrink-0">
34
33
  <button class="btn-action" @click="format()" v-if="isJsonFile()">
35
34
  <svg
36
35
  xmlns="http://www.w3.org/2000/svg"
@@ -68,6 +67,23 @@
68
67
  </svg>
69
68
  <span>Save File</span>
70
69
  </button>
70
+ <button class="btn-action" @click="closeFile">
71
+ <svg
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ width="16"
74
+ height="16"
75
+ viewBox="0 0 24 24"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ stroke-width="2"
79
+ stroke-linecap="round"
80
+ stroke-linejoin="round"
81
+ >
82
+ <line x1="18" y1="6" x2="6" y2="18"></line>
83
+ <line x1="6" y1="6" x2="18" y2="18"></line>
84
+ </svg>
85
+ <span>Close File</span>
86
+ </button>
71
87
  </div>
72
88
  </div>
73
89
  </div>
@@ -76,7 +92,7 @@
76
92
  <div v-if="textEditorVisible" class="my-3 relative">
77
93
  <div class="pb-4">
78
94
  <!-- Syntax Highlighting Editor -->
79
- <div class="editor-container">
95
+ <div class="editor-container table-component">
80
96
  <div class="editor-wrapper">
81
97
  <pre ref="codeDisplay" class="language-json code-highlight"></pre>
82
98
  <textarea
@@ -94,10 +110,17 @@
94
110
 
95
111
  <div v-if="errorMessage" class="error-container">
96
112
  <div class="error-icon">
97
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
98
- <circle cx="12" cy="12" r="10"/>
99
- <line x1="15" y1="9" x2="9" y2="15"/>
100
- <line x1="9" y1="9" x2="15" y2="15"/>
113
+ <svg
114
+ width="16"
115
+ height="16"
116
+ viewBox="0 0 24 24"
117
+ fill="none"
118
+ stroke="currentColor"
119
+ stroke-width="2"
120
+ >
121
+ <circle cx="12" cy="12" r="10" />
122
+ <line x1="15" y1="9" x2="9" y2="15" />
123
+ <line x1="9" y1="9" x2="15" y2="15" />
101
124
  </svg>
102
125
  </div>
103
126
  <div class="error-content">
@@ -110,9 +133,9 @@
110
133
  <div class="border border-dark-650 my-8" />
111
134
 
112
135
  <h5 class="text-white text-xl font-bold flex gap-x-3 mb-3">Proxy Editor</h5>
113
- <div class="flex justify-between items-center mb-4 relative z-60">
114
- <div class="w-64">
115
- <div class="relative">
136
+ <div class="flex justify-between items-center gap-4 proxy-controls-row mb-4">
137
+ <div class="w-64 flex flex-shrink-0 dropdown-container">
138
+ <div class="relative flex-grow">
116
139
  <Dropdown
117
140
  class="bg-dark-500 border-2 border-dark-550 rounded-lg w-full h-10"
118
141
  :default="ui.profile.proxyList?.checkout || proxyLists[0]"
@@ -124,7 +147,7 @@
124
147
  </div>
125
148
  </div>
126
149
  <!-- Proxy save button moved here -->
127
- <div v-if="currentProxyList" style="margin-right: 1px;">
150
+ <div v-if="currentProxyList" class="flex gap-x-2 flex-shrink-0">
128
151
  <button class="btn-action h-12" @click="saveProxies">
129
152
  <svg
130
153
  xmlns="http://www.w3.org/2000/svg"
@@ -143,28 +166,40 @@
143
166
  </svg>
144
167
  <span>Save Proxies</span>
145
168
  </button>
169
+ <button class="btn-action h-12" @click="closeProxyFile">
170
+ <svg
171
+ xmlns="http://www.w3.org/2000/svg"
172
+ width="16"
173
+ height="16"
174
+ viewBox="0 0 24 24"
175
+ fill="none"
176
+ stroke="currentColor"
177
+ stroke-width="2"
178
+ stroke-linecap="round"
179
+ stroke-linejoin="round"
180
+ >
181
+ <line x1="18" y1="6" x2="6" y2="18"></line>
182
+ <line x1="6" y1="6" x2="18" y2="18"></line>
183
+ </svg>
184
+ <span>Close File</span>
185
+ </button>
146
186
  </div>
147
187
  </div>
148
188
  <!-- Textarea -->
149
189
  <transition name="fade">
150
190
  <div v-if="currentProxyList" class="relative my-3">
151
191
  <div class="pb-4">
152
- <div class="proxy-editor-container">
153
- <textarea
154
- v-model="proxyContent"
155
- class="proxy-editor"
156
- spellcheck="false"
157
- ></textarea>
192
+ <div class="proxy-editor-container table-component">
193
+ <textarea v-model="proxyContent" class="proxy-editor" spellcheck="false"></textarea>
158
194
  </div>
159
195
  </div>
160
196
  </div>
161
197
  </transition>
162
198
  </div>
163
- <QuickSettings v-if="activeModal === 'quick-settings'" />
164
199
  </div>
165
200
  </template>
166
201
  <script setup>
167
- import { ref, computed, watch, onMounted, nextTick } from "vue";
202
+ import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
168
203
  import { DownIcon, ReloadIcon, EditIcon } from "@/components/icons";
169
204
  import { useUIStore } from "@/stores/ui";
170
205
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
@@ -402,6 +437,19 @@ const saveAll = async () => {
402
437
  await saveQuickConfig();
403
438
  };
404
439
 
440
+ const closeFile = () => {
441
+ currentFile.value = "";
442
+ currentContent.value = "";
443
+ textEditorVisible.value = false;
444
+ ui.logger.Info("File closed");
445
+ };
446
+
447
+ const closeProxyFile = () => {
448
+ currentProxyList.value = "";
449
+ proxyContent.value = "";
450
+ ui.logger.Info("Proxy file closed");
451
+ };
452
+
405
453
  const loadProxies = async (list) => {
406
454
  try {
407
455
  if (DEBUG) {
@@ -455,6 +503,47 @@ onMounted(() => {
455
503
  }
456
504
  });
457
505
 
506
+ // iOS-specific editor fixes with cursor tracking
507
+ if (
508
+ /iPad|iPhone|iPod/.test(navigator.platform) ||
509
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform))
510
+ ) {
511
+ const trackCursor = (editor) => {
512
+ const scrollToCursor = () => {
513
+ setTimeout(() => {
514
+ const container = editor.closest(".editor-container") || editor.closest(".proxy-editor-container");
515
+ if (container) {
516
+ // Get cursor position and scroll to it
517
+ const lines = editor.value.substring(0, editor.selectionStart).split("\n");
518
+ const lineHeight = 24; // Approximate line height
519
+ const cursorY = lines.length * lineHeight;
520
+ const containerHeight = container.clientHeight;
521
+
522
+ // Scroll to keep cursor in view
523
+ if (cursorY > container.scrollTop + containerHeight - 60) {
524
+ container.scrollTop = cursorY - containerHeight + 60;
525
+ }
526
+ }
527
+ }, 100);
528
+ };
529
+
530
+ editor.addEventListener("focus", scrollToCursor);
531
+ editor.addEventListener("input", scrollToCursor);
532
+ editor.addEventListener("click", scrollToCursor);
533
+ editor.addEventListener("keyup", scrollToCursor);
534
+ };
535
+
536
+ nextTick(() => {
537
+ if (codeEditor.value) {
538
+ trackCursor(codeEditor.value);
539
+ }
540
+ const proxyEditor = document.querySelector(".proxy-editor");
541
+ if (proxyEditor) {
542
+ trackCursor(proxyEditor);
543
+ }
544
+ });
545
+ }
546
+
458
547
  // Watch for editor visibility changes
459
548
  watch(textEditorVisible, (newValue) => {
460
549
  if (newValue) {
@@ -470,6 +559,8 @@ onMounted(() => {
470
559
  });
471
560
  });
472
561
 
562
+ // No cleanup needed - global iOS handling takes care of everything
563
+
473
564
  if (ui.profile.admin && !DEBUG) loadAvailableFiles();
474
565
  loadProxyLists();
475
566
  </script>
@@ -552,8 +643,9 @@ loadProxyLists();
552
643
  max-height: 600px;
553
644
  border-radius: 8px;
554
645
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
555
- overflow: hidden;
646
+ overflow: visible !important;
556
647
  background-color: #2e2f34;
648
+ touch-action: auto !important;
557
649
  }
558
650
 
559
651
  .editor-wrapper {
@@ -562,6 +654,7 @@ loadProxyLists();
562
654
  height: 100%;
563
655
  min-height: 300px;
564
656
  max-height: 600px;
657
+ overflow: visible !important; /* ALLOW SCROLLING */
565
658
  }
566
659
 
567
660
  .code-editor {
@@ -598,6 +691,33 @@ loadProxyLists();
598
691
  background: rgba(98, 114, 164, 0.4);
599
692
  }
600
693
 
694
+ /* iOS keyboard focus fix and scrolling */
695
+ .code-editor:focus,
696
+ .proxy-editor:focus {
697
+ transform: translateZ(0);
698
+ -webkit-transform: translateZ(0);
699
+ }
700
+
701
+ /* Force textarea scrolling to work */
702
+ .code-editor,
703
+ .proxy-editor {
704
+ overflow: auto !important;
705
+ -webkit-overflow-scrolling: touch !important;
706
+ user-select: text !important;
707
+ -webkit-user-select: text !important;
708
+ touch-action: auto !important;
709
+ overscroll-behavior: auto !important;
710
+ }
711
+
712
+ /* Additional override for any global styles */
713
+ textarea.code-editor,
714
+ textarea.proxy-editor {
715
+ overflow: auto !important;
716
+ -webkit-overflow-scrolling: touch !important;
717
+ touch-action: auto !important;
718
+ overscroll-behavior: auto !important;
719
+ }
720
+
601
721
  .code-highlight {
602
722
  width: 100%;
603
723
  height: 100%;
@@ -629,7 +749,8 @@ loadProxyLists();
629
749
 
630
750
  /* Proxy editor container */
631
751
  .proxy-editor-container {
632
- @apply w-full overflow-hidden rounded-lg border border-dark-600;
752
+ @apply w-full rounded-lg border border-dark-600;
753
+ overflow: visible !important; /* ALLOW SCROLLING */
633
754
  height: 400px;
634
755
  max-height: 60vh;
635
756
  background-color: #2e2f34;
@@ -668,7 +789,7 @@ loadProxyLists();
668
789
 
669
790
  .proxy-editor-container:focus-within {
670
791
  border-color: #5d7cc0;
671
- box-shadow: 0 0 0 2px rgba(93, 124, 192, 0.25);
792
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);
672
793
  }
673
794
 
674
795
  /* Prism.js dark theme */
@@ -842,4 +963,34 @@ pre[class*="language-"] {
842
963
  word-break: break-word;
843
964
  opacity: 0.9;
844
965
  }
966
+
967
+ /* iPhone portrait mode spacing fixes */
968
+ @media (max-width: 480px) and (orientation: portrait) {
969
+ .editor-controls-row {
970
+ gap: 0.75rem !important;
971
+ flex-wrap: wrap;
972
+ }
973
+
974
+ .dropdown-container {
975
+ width: 100% !important;
976
+ max-width: 16rem;
977
+ margin-bottom: 0.5rem;
978
+ }
979
+
980
+ .editor-controls-row > div:last-child {
981
+ margin-right: auto;
982
+ margin-top: 0.5rem;
983
+ }
984
+
985
+ /* Proxy editor responsive layout */
986
+ .proxy-controls-row {
987
+ gap: 0.75rem !important;
988
+ flex-wrap: wrap;
989
+ }
990
+
991
+ .proxy-controls-row > div:last-child {
992
+ margin-right: auto;
993
+ margin-top: 0.5rem;
994
+ }
995
+ }
845
996
  </style>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="filter-builder-container w-full min-h-0 flex flex-col overflow-hidden">
3
3
  <!-- Heading -->
4
- <div class="flex-between pt-4 pb-1 px-4 mt-1">
4
+ <div class="flex-between pt-4 pb-1 mt-1">
5
5
  <div class="flex-center gap-4">
6
6
  <FilterIcon class="cursor-pointer smooth-hover text-white" />
7
7
  <h4 class="text-heading">Filter creator</h4>
@@ -47,9 +47,22 @@
47
47
  v-else
48
48
  class="h-full flex items-center justify-center p-2 rounded shadow border-2 border-dark-550 svg-container"
49
49
  >
50
- <div class="text-center text-dark-400">
51
- <StadiumIcon class="mx-auto mb-2 w-8 h-8 opacity-50" />
52
- <p class="text-sm">Enter an event ID and click "Load" to display the venue map</p>
50
+ <div class="text-center">
51
+ <svg
52
+ class="w-12 h-12 mb-3 mx-auto opacity-50"
53
+ viewBox="0 0 19 19"
54
+ fill="none"
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ >
57
+ <path
58
+ d="M2.37499 5.54165V2.37498L5.54166 3.95831L2.37499 5.54165ZM14.25 5.54165V2.37498L17.4167 3.95831L14.25 5.54165ZM8.70833 4.74998V1.58331L11.875 3.16665L8.70833 4.74998ZM8.70833 17.4166C7.70555 17.3903 6.77218 17.3079 5.9082 17.1696C5.0437 17.0308 4.29162 16.8559 3.65195 16.6448C3.01176 16.4337 2.50694 16.1896 2.13749 15.9125C1.76805 15.6354 1.58333 15.3451 1.58333 15.0416V7.91665C1.58333 7.58678 1.79127 7.27988 2.20716 6.99594C2.62252 6.71252 3.18645 6.46183 3.89895 6.24385C4.61145 6.02641 5.4493 5.85488 6.4125 5.72927C7.37569 5.60419 8.40486 5.54165 9.49999 5.54165C10.5951 5.54165 11.6243 5.60419 12.5875 5.72927C13.5507 5.85488 14.3885 6.02641 15.101 6.24385C15.8135 6.46183 16.3775 6.71252 16.7928 6.99594C17.2087 7.27988 17.4167 7.58678 17.4167 7.91665V15.0416C17.4167 15.3451 17.2319 15.6354 16.8625 15.9125C16.493 16.1896 15.9885 16.4337 15.3488 16.6448C14.7086 16.8559 13.9565 17.0308 13.0926 17.1696C12.2281 17.3079 11.2944 17.3903 10.2917 17.4166V14.25H8.70833V17.4166ZM9.49999 8.70831C10.7799 8.70831 11.885 8.63231 12.8155 8.48031C13.7454 8.32884 14.4875 8.15415 15.0417 7.95623C15.0417 7.89026 14.5403 7.73509 13.5375 7.49073C12.5347 7.2469 11.1889 7.12498 9.49999 7.12498C7.81111 7.12498 6.46527 7.2469 5.46249 7.49073C4.45972 7.73509 3.95833 7.89026 3.95833 7.95623C4.51249 8.15415 5.25481 8.32884 6.18529 8.48031C7.11523 8.63231 8.22013 8.70831 9.49999 8.70831ZM7.12499 15.7146V12.6666H11.875V15.7146C12.9305 15.609 13.7948 15.4538 14.4677 15.2491C15.1406 15.0448 15.5958 14.8635 15.8333 14.7052V9.34165C15.1076 9.63192 14.1972 9.86283 13.1021 10.0344C12.0069 10.2059 10.8062 10.2916 9.49999 10.2916C8.19374 10.2916 6.99305 10.2059 5.89791 10.0344C4.80277 9.86283 3.89236 9.63192 3.16666 9.34165V14.7052C3.40416 14.8635 3.85937 15.0448 4.53229 15.2491C5.2052 15.4538 6.06944 15.609 7.12499 15.7146Z"
59
+ fill="#F5F5F5"
60
+ />
61
+ </svg>
62
+ <p class="text-light-400 text-sm">No Map</p>
63
+ <p class="text-light-500 text-xs">
64
+ Enter an event ID and click "Load" to display the venue map
65
+ </p>
53
66
  </div>
54
67
  </div>
55
68
  </div>
@@ -83,13 +96,13 @@
83
96
  <div class="flex items-center gap-2">
84
97
  <span class="text-base">Filters</span>
85
98
  <span class="text-base text-light-300">{{ filterBuilder.filters.length }}</span>
99
+ </div>
100
+ <div class="flex gap-2 items-center">
86
101
  <PriceSortToggle
87
102
  class="w-14 smooth-hover"
88
103
  :options="['All', 'WL', 'BL']"
89
104
  @change="(e) => (shownFilters = e)"
90
105
  />
91
- </div>
92
- <div class="flex gap-2 items-center">
93
106
  <button class="header-btn save-btn" @click="saveFilter">
94
107
  <EditIcon class="w-4 h-4" />
95
108
  <span class="lg:block hidden">Save</span>
@@ -167,7 +180,7 @@
167
180
  </div>
168
181
  </div>
169
182
 
170
- <transition-group name="fade" mode="out-in">
183
+ <transition-group name="fade">
171
184
  <FilterPreview v-if="activeModal === 'preview-filter'" :filter="filterBuilder" />
172
185
  </transition-group>
173
186
  </div>
@@ -562,6 +575,7 @@ const updateShownVenue = async () => {
562
575
 
563
576
  const loadFilter = async () => {
564
577
  try {
578
+ if (!eventId.value) return;
565
579
  const res = await fetch(`/api/filter/load?eventId=${eventId.value}`);
566
580
  const data = await res.json();
567
581
  if (eventId.value) ui.showSuccess("Loaded filter data");
@@ -23,18 +23,18 @@
23
23
  </button>
24
24
  </li>
25
25
  <li>
26
- <button
27
- :disabled="ui.disabledButtons['add-profiles']"
28
- @click="disable"
26
+ <button
27
+ :disabled="ui.disabledButtons['add-profiles']"
28
+ @click="disable"
29
29
  class="smooth-hover text-red-400"
30
30
  >
31
31
  <CloseXIcon />
32
32
  </button>
33
33
  </li>
34
34
  <li>
35
- <button
36
- :disabled="ui.disabledButtons['add-profiles']"
37
- @click="enable"
35
+ <button
36
+ :disabled="ui.disabledButtons['add-profiles']"
37
+ @click="enable"
38
38
  class="smooth-hover text-green-400"
39
39
  >
40
40
  <CheckIcon />
@@ -112,10 +112,10 @@
112
112
  </div>
113
113
 
114
114
  <!-- Tasks (Table) -->
115
- <ProfileView :tasks="processedTasks" />
115
+ <ProfileView :profiles="processedTasks" />
116
116
 
117
117
  <!-- Modal -->
118
- <transition-group name="fade" mode="out-in">
118
+ <transition-group name="fade">
119
119
  <CreateProfile v-if="activeModal === 'create-profile'" />
120
120
  </transition-group>
121
121
  </div>