@necrolab/dashboard 0.4.48 → 0.4.50

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 (42) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/.prettierrc +12 -1
  3. package/exit +209 -0
  4. package/index.html +1 -1
  5. package/package.json +1 -1
  6. package/public/manifest.json +8 -3
  7. package/src/assets/css/_input.scss +104 -111
  8. package/src/assets/css/_utilities.scss +441 -0
  9. package/src/assets/css/main.scss +228 -154
  10. package/src/components/Auth/LoginForm.vue +49 -6
  11. package/src/components/Editors/Account/Account.vue +156 -146
  12. package/src/components/Editors/Account/AccountCreator.vue +1 -1
  13. package/src/components/Editors/Account/AccountView.vue +13 -13
  14. package/src/components/Editors/Account/CreateAccount.vue +25 -16
  15. package/src/components/Editors/Profile/CreateProfile.vue +1 -1
  16. package/src/components/Editors/Profile/Profile.vue +1 -1
  17. package/src/components/Editors/Profile/ProfileCountryChooser.vue +83 -19
  18. package/src/components/Editors/Profile/ProfileView.vue +11 -11
  19. package/src/components/Tasks/CreateTaskAXS.vue +3 -3
  20. package/src/components/Tasks/CreateTaskTM.vue +7 -35
  21. package/src/components/Tasks/QuickSettings.vue +112 -9
  22. package/src/components/Tasks/Stats.vue +29 -26
  23. package/src/components/Tasks/Task.vue +499 -365
  24. package/src/components/Tasks/TaskView.vue +187 -127
  25. package/src/components/icons/Sandclock.vue +2 -2
  26. package/src/components/icons/Stadium.vue +1 -1
  27. package/src/components/ui/Modal.vue +37 -35
  28. package/src/components/ui/controls/CountryChooser.vue +200 -62
  29. package/src/components/ui/controls/atomic/Dropdown.vue +177 -91
  30. package/src/components/ui/controls/atomic/MultiDropdown.vue +247 -168
  31. package/src/composables/useClickOutside.js +21 -0
  32. package/src/composables/useDropdownPosition.js +174 -0
  33. package/src/stores/sampleData.js +4 -2
  34. package/src/stores/ui.js +8 -6
  35. package/src/views/Accounts.vue +12 -19
  36. package/src/views/Console.vue +34 -47
  37. package/src/views/Editor.vue +1194 -730
  38. package/src/views/Login.vue +65 -119
  39. package/src/views/Profiles.vue +2 -2
  40. package/src/views/Tasks.vue +170 -137
  41. package/static/offline.html +192 -50
  42. package/tailwind.config.js +47 -21
@@ -1,149 +1,95 @@
1
1
  <template>
2
- <div class="login-container" v-once>
3
- <div class="login-card">
4
- <div class="flex justify-center mb-8">
5
- <img src="@/assets/img/logo_trans.png" class="h-16 object-cover" alt="Logo: Necro" />
6
- </div>
7
- <h2 class="text-l text-white text-center font-bold mb-6">Please login to proceed</h2>
8
-
9
- <LoginForm />
10
- </div>
2
+ <div class="login-container" v-once>
3
+ <div class="login-card">
4
+ <div class="flex justify-center mb-8">
5
+ <img
6
+ src="@/assets/img/logo_trans.png"
7
+ class="h-16 object-cover"
8
+ alt="Logo: Necro"
9
+ />
10
+ </div>
11
+ <h2 class="text-l text-white text-center font-bold mb-6">
12
+ Please login to proceed
13
+ </h2>
14
+
15
+ <LoginForm />
11
16
  </div>
17
+ </div>
12
18
  </template>
13
19
  <script setup>
14
20
  import LoginForm from "@/components/Auth/LoginForm.vue";
15
21
  </script>
16
22
  <style lang="scss" scoped>
17
23
  .login-container {
18
- @apply flex flex-col items-center justify-center min-h-screen px-4;
19
- padding-top: 5vh;
20
- padding-bottom: 20vh;
21
-
22
- // Portrait mobile devices
23
- @media (max-width: 480px) and (orientation: portrait) {
24
- padding-top: 7vh;
25
- padding-bottom: 10vh;
26
- @apply px-4;
27
- }
28
-
29
- // Landscape mobile devices
30
- @media (max-width: 896px) and (orientation: landscape) and (max-height: 500px) {
31
- padding-top: 5vh;
32
- padding-bottom: 5vh;
33
- @apply px-6;
34
- }
35
-
36
- // Tablets portrait
37
- @media (min-width: 481px) and (max-width: 768px) and (orientation: portrait) {
38
- padding-top: 8vh;
39
- padding-bottom: 15vh;
40
- @apply px-8;
41
- }
42
-
43
- // Tablets landscape
44
- @media (min-width: 481px) and (max-width: 1024px) and (orientation: landscape) {
45
- padding-top: 4vh;
46
- padding-bottom: 10vh;
47
- @apply px-12;
48
- }
49
-
50
- // Desktop
51
- @media (min-width: 1025px) {
52
- padding-top: 8vh;
53
- padding-bottom: 22vh;
54
- }
55
-
56
- // Very short screens (landscape phones, short laptops)
57
- @media (max-height: 600px) {
58
- padding-top: 3vh;
59
- padding-bottom: 3vh;
60
- }
61
-
62
- // Very tall screens
63
- @media (min-height: 900px) {
64
- padding-top: 10vh;
65
- padding-bottom: 30vh;
66
- }
24
+ @apply flex flex-col items-center min-h-screen px-4;
25
+ margin-top: 3vh;
26
+
27
+ // Mobile devices
28
+ @media (max-width: 480px) {
29
+ margin-top: 2vh;
30
+ }
31
+
32
+ // Landscape mode
33
+ @media (orientation: landscape) {
34
+ margin-top: 1vh;
35
+ justify-content: flex-start;
36
+ min-height: 100vh;
37
+ min-height: 100dvh;
38
+ }
67
39
  }
68
40
 
69
41
  .login-card {
70
- @apply bg-dark-400 border border-dark-650 rounded-lg shadow-xl;
71
- backdrop-filter: blur(10px);
72
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
73
- width: 100%;
74
- max-width: 420px;
75
- padding: 2rem;
42
+ @apply bg-dark-400 border border-dark-650 rounded-lg shadow-xl;
43
+ backdrop-filter: blur(10px);
44
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
45
+ width: 100%;
46
+ max-width: 420px;
47
+ padding: 1.75rem;
76
48
 
77
- // Small mobile devices
78
- @media (max-width: 380px) {
79
- max-width: 340px;
80
- padding: 1.5rem;
81
- @apply rounded-md;
82
- }
83
-
84
- // Large mobile devices
85
- @media (min-width: 381px) and (max-width: 480px) {
86
- max-width: 380px;
87
- padding: 1.75rem;
88
- }
89
-
90
- // Tablets and up
91
- @media (min-width: 481px) {
92
- max-width: 420px;
93
- padding: 2rem;
94
- }
95
-
96
- // Landscape mobile - make card more compact
97
- @media (max-height: 500px) and (orientation: landscape) {
98
- padding: 1.25rem;
99
-
100
- h2 {
101
- @apply text-xl mb-4;
102
- }
49
+ // Mobile devices
50
+ @media (max-width: 480px) {
51
+ max-width: 380px;
52
+ padding: 1.5rem;
53
+ }
103
54
 
104
- .flex.justify-center {
105
- @apply mb-4;
55
+ // Landscape mode
56
+ @media (orientation: landscape) {
57
+ padding: 1.25rem;
58
+ margin: 0.25rem 0;
106
59
 
107
- img {
108
- @apply h-12;
109
- }
110
- }
60
+ h2 {
61
+ @apply text-lg mb-2;
111
62
  }
112
63
 
113
- // Very small screens (iPhone SE, etc.)
114
- @media (max-width: 320px) {
115
- max-width: 300px;
116
- padding: 1.25rem;
64
+ .flex.justify-center {
65
+ @apply mb-2;
117
66
 
118
- h2 {
119
- @apply text-lg;
120
- }
121
-
122
- img {
123
- @apply h-12;
124
- }
67
+ img {
68
+ @apply h-10;
69
+ }
125
70
  }
71
+ }
126
72
  }
127
73
 
128
74
  // Logo responsive sizing
129
75
  .login-card img {
130
- @media (max-width: 480px) {
131
- @apply h-14;
132
- }
76
+ @media (max-width: 480px) {
77
+ @apply h-12;
78
+ }
133
79
 
134
- @media (max-height: 500px) and (orientation: landscape) {
135
- @apply h-10;
136
- }
80
+ @media (orientation: landscape) {
81
+ @apply h-10;
82
+ }
137
83
  }
138
84
 
139
85
  // Title responsive sizing
140
86
  .login-card h2 {
141
- @media (max-width: 480px) {
142
- @apply text-xl mb-4;
143
- }
87
+ @media (max-width: 480px) {
88
+ @apply text-lg mb-3;
89
+ }
144
90
 
145
- @media (max-height: 500px) and (orientation: landscape) {
146
- @apply text-lg mb-3;
147
- }
91
+ @media (orientation: landscape) {
92
+ @apply text-base mb-2;
93
+ }
148
94
  }
149
95
  </style>
@@ -68,13 +68,13 @@
68
68
  @change="(f) => (ui.search.profiles.tag = f)"
69
69
  /> -->
70
70
  <Dropdown
71
- class="rounded-r-lg w-32 bg-dark-500 relative z-50 border-2 border-dark-600 search-dropdown"
72
- style="margin-left: 0 !important; border-width: 2px !important"
71
+ class="dropdown-base rounded-r-lg w-32 relative z-50 search-dropdown"
73
72
  rightAmount="right-1"
74
73
  default="Any"
75
74
  :value="ui.search.profiles.tag"
76
75
  :onClick="(f) => (ui.search.profiles.tag = f)"
77
76
  :options="allTags"
77
+ :includeAdjacentButtons="true"
78
78
  :capitalize="true"
79
79
  />
80
80
  </div>
@@ -1,157 +1,185 @@
1
1
  <template>
2
- <div class="tasks-page">
3
- <div class="flex-between pt-5 pb-2">
4
- <div class="flex-center gap-4">
5
- <GearIcon class="w-5 cursor-pointer smooth-hover" @click="ui.toggleModal('quick-settings')" />
6
- <h4 class="text-heading">
7
- Tasks
8
- <span class="text-subheading pl-1">{{ taskCount }}</span>
9
- </h4>
10
- </div>
11
- <ul class="mobile-icons mobile-header-controls">
12
- <li>
13
- <button @click="ui.startTasks()"><PlayIcon class="w-4 h-4" /></button>
14
- </li>
15
- <li>
16
- <button @click="ui.stopTasks()"><PauseIcon class="w-4 h-4" /></button>
17
- </li>
18
- <li>
19
- <button
20
- class="text-sm"
21
- :disabled="ui.disabledButtons['add-tasks']"
22
- @click="ui.toggleModal('create-task')"
23
- >
24
- <PlusIcon class="w-4 h-4" />
25
- </button>
26
- </li>
27
- <li>
28
- <button @click="ui.deleteTasks()"><TrashIcon class="h-3.5 w-3.5" /></button>
29
- </li>
30
- </ul>
31
- </div>
2
+ <div class="tasks-page">
3
+ <div class="flex-between pb-2" style="padding-top: 1.5rem">
4
+ <div class="flex-center gap-4">
5
+ <GearIcon
6
+ class="w-5 cursor-pointer smooth-hover"
7
+ @click="ui.toggleModal('quick-settings')"
8
+ />
9
+ <h4 class="text-heading">
10
+ Tasks
11
+ <span class="text-subheading pl-1">{{ taskCount }}</span>
12
+ </h4>
13
+ </div>
14
+ <ul class="mobile-icons mobile-header-controls">
15
+ <li>
16
+ <button @click="ui.startTasks()"><PlayIcon class="w-4 h-4" /></button>
17
+ </li>
18
+ <li>
19
+ <button @click="ui.stopTasks()"><PauseIcon class="w-4 h-4" /></button>
20
+ </li>
21
+ <li>
22
+ <button
23
+ class="text-sm"
24
+ :disabled="ui.disabledButtons['add-tasks']"
25
+ @click="ui.toggleModal('create-task')"
26
+ >
27
+ <PlusIcon class="w-4 h-4" />
28
+ </button>
29
+ </li>
30
+ <li>
31
+ <button @click="ui.deleteTasks()"><TrashIcon class="h-3.5 w-3.5" /></button>
32
+ </li>
33
+ </ul>
34
+ </div>
32
35
 
33
- <Stats class="stats-component" />
36
+ <div class="controls-wrapper">
37
+ <Stats class="stats-component" />
34
38
 
35
- <div class="controls-wrapper lg:mb-6 mb-3">
36
- <DesktopControls
37
- class="desktop-controls-hide"
38
- @stopAll="ui.stopTasks()"
39
- @startAll="ui.startTasks()"
40
- @deleteAll="ui.deleteTasks()"
41
- />
42
- </div>
39
+ <div class="controls-wrapper lg:mb-3">
40
+ <DesktopControls
41
+ class="desktop-controls-hide"
42
+ @stopAll="ui.stopTasks()"
43
+ @startAll="ui.startTasks()"
44
+ @deleteAll="ui.deleteTasks()"
45
+ />
46
+ </div>
43
47
 
44
- <div class="flex items-center justify-between gap-2 lg:mb-2 mb-1">
45
- <div v-if="uniqEventIds.length > 1" class="flex-1 md:min-w-96 md:max-w-96 md:flex-none">
46
- <Dropdown
47
- :onClick="(f) => ui.setCurrentEvent(f)"
48
- default="All events"
49
- :chosen="ui.currentEvent"
50
- :options="uniqEventIds"
51
- :allowDefault="true"
52
- class="input-default w-full hover:bg-dark-400 h-10"
53
- rightAmount="right-2"
54
- />
55
- </div>
56
- <PriceSortToggle
57
- class="min-w-24 max-w-28 flex-shrink-0"
58
- :options="['All', 'Checkout']"
59
- :darker="true"
60
- :current="ui.taskFilter"
61
- @change="(e) => ui.setTaskFilter(e)"
62
- />
48
+ <div class="flex items-center justify-between gap-2 lg:mb-2 mb-1 filter-controls">
49
+ <div
50
+ v-if="uniqEventIds.length > 1"
51
+ class="flex-1 md:min-w-96 md:max-w-96 md:flex-none min-w-0"
52
+ >
53
+ <Dropdown
54
+ :onClick="(f) => ui.setCurrentEvent(f)"
55
+ default="All events"
56
+ :chosen="ui.currentEvent"
57
+ :options="uniqEventIds"
58
+ :allowDefault="true"
59
+ class="input-default w-full hover:bg-dark-400 event-dropdown"
60
+ rightAmount="right-2"
61
+ />
63
62
  </div>
63
+ <PriceSortToggle
64
+ class="min-w-24 max-w-28 flex-shrink-0"
65
+ :options="['All', 'Checkout']"
66
+ :darker="true"
67
+ :current="ui.taskFilter"
68
+ @change="(e) => ui.setTaskFilter(e)"
69
+ />
70
+ </div>
64
71
 
65
- <TaskView class="lg:mb-6 mb-3" :tasks="ui.tasks" />
72
+ <TaskView class="lg:mb-6 mb-3" :tasks="ui.tasks" />
66
73
 
67
- <Utilities class="utilities-section" />
68
-
69
- <transition-group name="fade">
70
- <CreateTaskTM v-if="ui.currentModule == 'TM' && activeModal === 'create-task'" @new="ui.addNewTask" />
71
- <CreateTaskAXS v-if="ui.currentModule == 'AXS' && activeModal === 'create-task'" @new="ui.addNewTask" />
72
- <CheckStock v-if="activeModal === 'check-stock'" />
73
- <ScrapeVenue v-if="activeModal === 'scrape-venue'" />
74
- <MassEditPresaleCode v-if="activeModal === 'mass-edit-presale-code'" />
75
- <QuickSettings v-if="activeModal === 'quick-settings'" />
76
- </transition-group>
74
+ <Utilities class="utilities-section" />
77
75
  </div>
76
+
77
+ <transition-group name="fade">
78
+ <CreateTaskTM
79
+ v-if="ui.currentModule == 'TM' && activeModal === 'create-task'"
80
+ @new="ui.addNewTask"
81
+ />
82
+ <CreateTaskAXS
83
+ v-if="ui.currentModule == 'AXS' && activeModal === 'create-task'"
84
+ @new="ui.addNewTask"
85
+ />
86
+ <CheckStock v-if="activeModal === 'check-stock'" />
87
+ <ScrapeVenue v-if="activeModal === 'scrape-venue'" />
88
+ <MassEditPresaleCode v-if="activeModal === 'mass-edit-presale-code'" />
89
+ <QuickSettings v-if="activeModal === 'quick-settings'" />
90
+ </transition-group>
91
+ </div>
78
92
  </template>
79
93
  <style lang="scss" scoped>
80
94
  .custom-dropdown-content {
81
- top: 2.6rem !important;
82
- left: -13px;
83
- @apply border border-dark-650;
95
+ top: 2.6rem !important;
96
+ left: -13px;
97
+ @apply border border-dark-650;
84
98
  }
85
99
 
100
+ /* ==========================================================================
101
+ TASKS PAGE RESPONSIVE LAYOUT - MOBILE FIRST
102
+ ========================================================================== */
103
+
104
+ /* Default mobile layout */
86
105
  .desktop-controls-hide {
87
- display: none !important;
106
+ display: none;
88
107
  }
89
108
 
90
109
  .mobile-header-controls {
91
- display: flex !important;
110
+ display: flex;
92
111
  }
93
112
 
94
- @media (min-width: 650px) {
95
- .desktop-controls-hide {
96
- display: flex !important;
97
- }
113
+ /* Event dropdown base styling */
114
+ .event-dropdown {
115
+ min-width: 0;
116
+ /* Match PriceSortToggle height instead of input-default */
117
+ height: 40px !important;
118
+ }
119
+
120
+ .event-dropdown .dropdown-value {
121
+ max-width: 100%;
122
+ overflow: hidden;
123
+ text-overflow: ellipsis;
124
+ white-space: nowrap;
125
+ }
126
+
127
+ /* Small mobile screens (portrait) */
128
+ @media (max-width: 480px) and (orientation: portrait) {
129
+ .event-dropdown {
130
+ height: 40px !important; /* Match PriceSortToggle height exactly */
131
+ }
98
132
 
99
- .mobile-header-controls {
100
- display: none !important;
101
- }
133
+ .event-dropdown .dropdown-value {
134
+ max-width: calc(100vw - 140px);
135
+ font-size: 0.875rem;
136
+ }
137
+
138
+ .event-dropdown .dropdown-display {
139
+ padding-right: 2rem;
140
+ }
141
+ }
142
+
143
+ /* Extra small screens */
144
+ @media (max-width: 375px) and (orientation: portrait) {
145
+ .event-dropdown {
146
+ height: 40px !important; /* Maintain 40px height to match PriceSortToggle */
147
+ }
148
+
149
+ .event-dropdown .dropdown-value {
150
+ max-width: calc(100vw - 120px);
151
+ font-size: 0.8rem;
152
+ }
102
153
  }
103
154
 
104
- /* iPhone landscape mode optimizations */
105
- @media screen and (max-height: 500px) and (orientation: landscape) {
106
- /* Hide Stats component */
107
- .stats-component {
108
- display: none !important;
109
- }
110
-
111
- /* Hide Utilities section (Scrape Venue, Check Stock) */
112
- .utilities-section {
113
- display: none !important;
114
- }
115
-
116
- /* Hide event filter and price sort toggle row */
117
- .flex.items-center.justify-between.gap-2 {
118
- display: none !important;
119
- }
120
-
121
- /* Hide desktop controls and show mobile controls */
122
- .desktop-controls-hide {
123
- display: none !important;
124
- }
125
-
126
- .mobile-header-controls {
127
- display: flex !important;
128
- }
129
-
130
- /* Hide the controls-wrapper completely */
131
- .controls-wrapper {
132
- display: none !important;
133
- }
134
-
135
- /* Reduce all margins to save space but keep proper navbar spacing */
136
- .lg\\:mb-6, .mb-3 {
137
- margin-bottom: 0.25rem !important;
138
- }
139
-
140
- /* Make header more compact but keep safe distance from navbar */
141
- .flex-between.pt-5.pb-2 {
142
- padding-top: 1rem !important; // Keep distance from navbar
143
- padding-bottom: 0.5rem !important;
144
- margin-bottom: 0.5rem !important;
145
- }
146
-
147
- /* Ensure task view has proper spacing */
148
- .lg\\:mb-6.mb-3 {
149
- margin-bottom: 0.25rem !important;
150
- }
155
+ /* Mobile landscape - hide non-essential elements */
156
+ @media (max-height: 500px) and (orientation: landscape) {
157
+ .stats-component,
158
+ .utilities-section,
159
+ .filter-controls {
160
+ display: none;
161
+ }
162
+
163
+ .flex-between.pb-2 {
164
+ padding-top: 1rem !important;
165
+ padding-bottom: 0.25rem;
166
+ margin-bottom: 0.25rem;
167
+ }
168
+ }
169
+
170
+ /* Tablet and small desktop - show desktop controls */
171
+ @media (min-width: 650px) {
172
+ .desktop-controls-hide {
173
+ display: flex;
174
+ }
175
+
176
+ .mobile-header-controls {
177
+ display: none;
178
+ }
151
179
  }
152
180
  </style>
153
181
  <script setup>
154
- import { computed } from "vue";
182
+ import { computed, onMounted } from "vue";
155
183
  import { DesktopControls } from "@/components/Tasks/Controls";
156
184
  import TaskView from "@/components/Tasks/TaskView.vue";
157
185
  import Utilities from "@/components/Tasks/Utilities.vue";
@@ -172,14 +200,19 @@ const activeModal = computed(() => ui.activeModal);
172
200
  const taskCount = computed(() => Object.keys(ui.getSelectedTasks()).length);
173
201
  ui.refreshQueueStats();
174
202
 
203
+ // Ensure "All events" is always selected on page load
204
+ onMounted(() => {
205
+ ui.setCurrentEvent("");
206
+ });
207
+
175
208
  const uniqEventIds = computed(() => {
176
- const ids = [
177
- ...new Set(
178
- Object.values(ui.tasks)
179
- .filter((t) => t.siteId === ui.currentCountry.siteId && !t.eventId.includes("@"))
180
- .map((v) => `${v.eventName} (${v.eventId})`)
181
- )
182
- ];
183
- return ids;
209
+ const ids = [
210
+ ...new Set(
211
+ Object.values(ui.tasks)
212
+ .filter((t) => t.siteId === ui.currentCountry.siteId && !t.eventId.includes("@"))
213
+ .map((v) => `${v.eventName} (${v.eventId})`)
214
+ ),
215
+ ];
216
+ return ids;
184
217
  });
185
218
  </script>