@necrolab/dashboard 0.5.12 → 0.5.14
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.
- package/.playwright-mcp/verification-accounts-desktop.png +0 -0
- package/.playwright-mcp/verification-tasks-desktop.png +0 -0
- package/.playwright-mcp/verification-tasks-mobile.png +0 -0
- package/README.md +21 -0
- package/docs/plans/2026-02-08-tailwind-consolidation-results.md +476 -0
- package/docs/plans/2026-02-08-tailwind-consolidation.md +2416 -0
- package/package.json +1 -1
- package/src/App.vue +2 -163
- package/src/assets/css/components/buttons.scss +43 -95
- package/src/assets/css/components/forms.scss +10 -28
- package/src/assets/css/components/search-groups.scss +80 -0
- package/src/assets/css/components/tables.scss +0 -8
- package/src/assets/css/main.scss +2 -43
- package/src/components/Editors/Account/Account.vue +14 -220
- package/src/components/Editors/Account/AccountCreator.vue +0 -4
- package/src/components/Editors/Account/AccountView.vue +0 -1
- package/src/components/Editors/Account/CreateAccount.vue +36 -107
- package/src/components/Editors/Profile/CreateProfile.vue +46 -135
- package/src/components/Editors/Profile/Profile.vue +10 -213
- package/src/components/Editors/Profile/ProfileView.vue +0 -1
- package/src/components/Filter/Filter.vue +14 -17
- package/src/components/Filter/FilterPreview.vue +0 -6
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +2 -16
- package/src/components/Tasks/CreateTaskAXS.vue +45 -104
- package/src/components/Tasks/CreateTaskTM.vue +58 -96
- package/src/components/Tasks/Task.vue +22 -209
- package/src/components/Tasks/TaskView.vue +5 -4
- package/src/components/Tasks/ViewTask.vue +201 -214
- package/src/components/icons/Copy.vue +6 -0
- package/src/components/icons/index.js +3 -1
- package/src/components/ui/ActionButtonGroup.vue +70 -0
- package/src/components/ui/FormField.vue +74 -0
- package/src/components/ui/InfoRow.vue +100 -0
- package/src/components/ui/Modal.vue +6 -47
- package/src/components/ui/Navbar.vue +15 -43
- package/src/components/ui/ReconnectIndicator.vue +4 -4
- package/src/components/ui/SectionCard.vue +24 -0
- package/src/components/ui/Splash.vue +1 -6
- package/src/components/ui/StatusBadge.vue +37 -0
- package/src/components/ui/controls/CountryChooser.vue +14 -58
- package/src/components/ui/controls/atomic/Dropdown.vue +16 -24
- package/src/components/ui/controls/atomic/MultiDropdown.vue +7 -1
- package/src/components/ui/controls/atomic/Switch.vue +13 -29
- package/src/composables/useCopyToClipboard.js +25 -0
- package/src/composables/useRowSelection.js +48 -0
- package/src/views/Accounts.vue +0 -81
- package/src/views/Console.vue +4 -21
- package/src/views/Editor.vue +48 -138
- package/src/views/FilterBuilder.vue +0 -23
- package/src/views/Login.vue +14 -63
- package/src/views/Profiles.vue +0 -82
- package/src/views/Tasks.vue +3 -24
- package/tailwind.config.js +47 -5
|
@@ -1,190 +1,214 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Modal>
|
|
3
3
|
<template #header>
|
|
4
|
-
<span class="flex">
|
|
4
|
+
<span class="flex">
|
|
5
|
+
View Task
|
|
6
|
+
<EyeIcon class="ml-4 w-4" />
|
|
7
|
+
</span>
|
|
5
8
|
</template>
|
|
6
9
|
|
|
10
|
+
<!-- Task data updates in real-time via socket.
|
|
11
|
+
If task is deleted from table, modal retains last known data -->
|
|
12
|
+
|
|
7
13
|
<!-- Task Overview Card -->
|
|
8
|
-
<
|
|
9
|
-
<h3 class="section-title">Task Overview</h3>
|
|
14
|
+
<SectionCard title="Task Overview" class="mb-4 mt-4">
|
|
10
15
|
<div class="grid grid-cols-1 gap-3">
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
16
|
+
<InfoRow
|
|
17
|
+
icon-text="#"
|
|
18
|
+
label="Task ID"
|
|
19
|
+
:value="taskSnapshot.taskId"
|
|
20
|
+
copyable
|
|
21
|
+
:copy-value="taskSnapshot.taskId"
|
|
22
|
+
copy-message="Copied task ID" />
|
|
23
|
+
<InfoRow
|
|
24
|
+
:icon="StadiumIcon"
|
|
25
|
+
label="Event ID"
|
|
26
|
+
:value="taskSnapshot.eventId"
|
|
27
|
+
copyable
|
|
28
|
+
:copy-value="taskSnapshot.eventId"
|
|
29
|
+
copy-message="Copied event ID" />
|
|
30
|
+
<InfoRow
|
|
31
|
+
v-if="taskSnapshot.eventName"
|
|
32
|
+
:icon="StadiumWhiteIcon"
|
|
33
|
+
label="Event Name"
|
|
34
|
+
:value="taskSnapshot.eventName" />
|
|
35
|
+
<InfoRow
|
|
36
|
+
v-if="taskSnapshot.venueName || taskSnapshot.eventCity"
|
|
37
|
+
:icon="StadiumWhiteIcon"
|
|
38
|
+
label="Venue"
|
|
39
|
+
:value="[taskSnapshot.venueName, taskSnapshot.eventCity].filter(Boolean).join(', ')" />
|
|
40
|
+
<InfoRow
|
|
41
|
+
v-if="taskSnapshot.eventLocalDate"
|
|
42
|
+
:icon="StadiumWhiteIcon"
|
|
43
|
+
label="Date"
|
|
44
|
+
:value="formatDate(taskSnapshot.eventLocalDate)" />
|
|
45
|
+
<InfoRow :icon="StatusIcon" label="Status" value-class="uppercase font-semibold flex items-center gap-2">
|
|
46
|
+
<div class="status-indicator flex-shrink-0" :class="colorToClass(taskSnapshot.active || taskSnapshot.status.toLowerCase() === 'idle' ? taskSnapshot.statusColor : 'red')"></div>
|
|
47
|
+
<span>{{ taskSnapshot.status }}</span>
|
|
48
|
+
</InfoRow>
|
|
49
|
+
<InfoRow
|
|
50
|
+
v-if="taskSnapshot._timeLeftString && taskSnapshot._timeLeftString !== 'No Cartholds'"
|
|
51
|
+
:icon="TimerIcon"
|
|
52
|
+
label="Cart Expiration"
|
|
53
|
+
:value-class="`font-semibold text-right ${taskSnapshot._timeLeftString === '00:00' ? 'text-red-400' : ''}`">
|
|
54
|
+
{{ taskSnapshot._timeLeftString !== "00:00" ? taskSnapshot._timeLeftString : "Expired" }}
|
|
55
|
+
</InfoRow>
|
|
56
|
+
<InfoRow
|
|
57
|
+
v-if="taskSnapshot.reservedTicketsList"
|
|
58
|
+
:icon="TicketIcon"
|
|
59
|
+
label="Reserved Tickets"
|
|
60
|
+
align-start
|
|
61
|
+
value-class="text-right flex-1">
|
|
62
|
+
<div
|
|
63
|
+
v-for="(line, index) in taskSnapshot.reservedTicketsList.split('\n')"
|
|
64
|
+
:key="index"
|
|
65
|
+
class="text-xs leading-tight">
|
|
66
|
+
<span
|
|
67
|
+
v-if="line.trim()"
|
|
68
|
+
:class="{
|
|
69
|
+
'font-bold text-green-400': isTotalPrice(
|
|
70
|
+
line,
|
|
71
|
+
index,
|
|
72
|
+
taskSnapshot.reservedTicketsList.split('\n')
|
|
73
|
+
)
|
|
74
|
+
}">
|
|
75
|
+
{{ line.trim() }}
|
|
76
|
+
</span>
|
|
57
77
|
</div>
|
|
58
|
-
</
|
|
78
|
+
</InfoRow>
|
|
59
79
|
</div>
|
|
60
|
-
</
|
|
80
|
+
</SectionCard>
|
|
61
81
|
|
|
62
82
|
<!-- Account Details Card -->
|
|
63
|
-
<
|
|
64
|
-
<h3 class="section-title">Account Details</h3>
|
|
83
|
+
<SectionCard title="Account Details" class="mb-4">
|
|
65
84
|
<div class="grid grid-cols-1 gap-3">
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
<InfoRow
|
|
86
|
+
v-if="taskSnapshot.email"
|
|
87
|
+
:icon="MailIcon"
|
|
88
|
+
label="Email"
|
|
89
|
+
:value="taskSnapshot.email"
|
|
90
|
+
copyable
|
|
91
|
+
:copy-value="taskSnapshot.email"
|
|
92
|
+
copy-message="Copied email" />
|
|
93
|
+
<InfoRow
|
|
94
|
+
v-if="taskSnapshot.password"
|
|
95
|
+
:icon="KeyIcon"
|
|
96
|
+
label="Password">
|
|
97
|
+
{{ showPassword ? taskSnapshot.password : "••••••••" }}
|
|
98
|
+
<template #action>
|
|
99
|
+
<button @click.stop="showPassword = !showPassword" class="flex items-center justify-center w-8 h-8 rounded transition-all hover:bg-dark-600">
|
|
100
|
+
<EyeToggle :visible="showPassword" />
|
|
101
|
+
</button>
|
|
102
|
+
<button @click.stop="copy(taskSnapshot.password, 'Copied password')" class="copy-button flex items-center justify-center w-8 h-8 rounded transition-all hover:bg-dark-600 text-light-500 hover:text-light-300">
|
|
103
|
+
<CopyIcon class="w-4 h-4" />
|
|
104
|
+
</button>
|
|
105
|
+
</template>
|
|
106
|
+
</InfoRow>
|
|
107
|
+
<InfoRow
|
|
108
|
+
v-if="!taskSnapshot.email && !taskSnapshot.password"
|
|
109
|
+
:icon="MailIcon"
|
|
110
|
+
label="Account"
|
|
111
|
+
value="No account chosen"
|
|
112
|
+
value-class="text-light-500" />
|
|
113
|
+
<InfoRow
|
|
114
|
+
v-if="taskSnapshot.profileName"
|
|
115
|
+
:icon="ProfileIcon"
|
|
116
|
+
label="Profile"
|
|
117
|
+
:value="taskSnapshot.profileName" />
|
|
89
118
|
</div>
|
|
90
|
-
</
|
|
119
|
+
</SectionCard>
|
|
91
120
|
|
|
92
121
|
<!-- Task Configuration -->
|
|
93
|
-
<
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<span class="info-value copyable">{{ task.presaleCode }}</span>
|
|
115
|
-
</div>
|
|
116
|
-
<div class="info-row" v-if="task.startOffset">
|
|
117
|
-
<ShieldIcon class="info-icon" />
|
|
118
|
-
<span class="info-label">Start Offset</span>
|
|
119
|
-
<span class="info-value">{{ task.startOffset }} min</span>
|
|
120
|
-
</div>
|
|
122
|
+
<SectionCard title="Task Configuration" class="mb-4">
|
|
123
|
+
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
|
124
|
+
<InfoRow :icon="BagIcon" label="Ticket Quantity" :value="String(taskSnapshot.quantity)" />
|
|
125
|
+
<InfoRow :icon="ScannerIcon" label="Account Tag" :value="taskSnapshot.accountTag || 'None'" />
|
|
126
|
+
<InfoRow
|
|
127
|
+
:icon="SellIcon"
|
|
128
|
+
label="Profile Tags"
|
|
129
|
+
:value="taskSnapshot.profileTags?.join(', ') || 'None'" />
|
|
130
|
+
<InfoRow
|
|
131
|
+
v-if="taskSnapshot.presaleCode"
|
|
132
|
+
:icon="PencilIcon"
|
|
133
|
+
label="Presale Code"
|
|
134
|
+
:value="taskSnapshot.presaleCode"
|
|
135
|
+
copyable
|
|
136
|
+
:copy-value="taskSnapshot.presaleCode"
|
|
137
|
+
copy-message="Copied presale code" />
|
|
138
|
+
<InfoRow
|
|
139
|
+
v-if="taskSnapshot.startOffset"
|
|
140
|
+
:icon="ShieldIcon"
|
|
141
|
+
label="Start Offset"
|
|
142
|
+
:value="`${taskSnapshot.startOffset} min`" />
|
|
121
143
|
</div>
|
|
122
|
-
</
|
|
144
|
+
</SectionCard>
|
|
123
145
|
|
|
124
146
|
<!-- Task Settings Toggles -->
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
|
147
|
+
<SectionCard title="Task Settings" class="mb-4">
|
|
148
|
+
<div class="grid grid-cols-2 gap-4 md:grid-cols-3">
|
|
128
149
|
<div class="toggle-item">
|
|
129
150
|
<div class="toggle-label">
|
|
130
|
-
<TimerIcon class="
|
|
151
|
+
<TimerIcon class="h-4 w-4" />
|
|
131
152
|
<span>Smart Timer</span>
|
|
132
153
|
</div>
|
|
133
154
|
<Switch v-model="toggles.smartTimer" disabled />
|
|
134
155
|
</div>
|
|
135
156
|
<div class="toggle-item">
|
|
136
157
|
<div class="toggle-label">
|
|
137
|
-
<GroupIcon class="
|
|
158
|
+
<GroupIcon class="h-4 w-4" />
|
|
138
159
|
<span>Login Later</span>
|
|
139
160
|
</div>
|
|
140
161
|
<Switch v-model="toggles.loginAfterCart" disabled />
|
|
141
162
|
</div>
|
|
142
163
|
<div class="toggle-item">
|
|
143
164
|
<div class="toggle-label">
|
|
144
|
-
<HandIcon class="
|
|
165
|
+
<HandIcon class="h-4 w-4" />
|
|
145
166
|
<span>Manual</span>
|
|
146
167
|
</div>
|
|
147
168
|
<Switch v-model="toggles.manual" disabled />
|
|
148
169
|
</div>
|
|
149
170
|
<div class="toggle-item">
|
|
150
171
|
<div class="toggle-label">
|
|
151
|
-
<SavingsIcon class="
|
|
172
|
+
<SavingsIcon class="h-4 w-4" />
|
|
152
173
|
<span>Do Not Pay</span>
|
|
153
174
|
</div>
|
|
154
175
|
<Switch v-model="toggles.doNotPay" disabled />
|
|
155
176
|
</div>
|
|
156
177
|
<div class="toggle-item">
|
|
157
178
|
<div class="toggle-label">
|
|
158
|
-
<LoyaltyIcon class="
|
|
179
|
+
<LoyaltyIcon class="h-4 w-4" />
|
|
159
180
|
<span>Presale Mode</span>
|
|
160
181
|
</div>
|
|
161
182
|
<Switch v-model="toggles.presaleMode" disabled />
|
|
162
183
|
</div>
|
|
163
184
|
<div class="toggle-item">
|
|
164
185
|
<div class="toggle-label">
|
|
165
|
-
<SkiIcon class="
|
|
186
|
+
<SkiIcon class="h-4 w-4" />
|
|
166
187
|
<span>Quick Queue</span>
|
|
167
188
|
</div>
|
|
168
189
|
<Switch v-model="toggles.quickQueue" disabled />
|
|
169
190
|
</div>
|
|
170
191
|
<div class="toggle-item">
|
|
171
192
|
<div class="toggle-label">
|
|
172
|
-
<SandclockIcon class="
|
|
193
|
+
<SandclockIcon class="h-4 w-4" />
|
|
173
194
|
<span>Aged Account</span>
|
|
174
195
|
</div>
|
|
175
196
|
<Switch v-model="toggles.agedAccount" disabled />
|
|
176
197
|
</div>
|
|
177
198
|
</div>
|
|
178
|
-
</
|
|
199
|
+
</SectionCard>
|
|
179
200
|
</Modal>
|
|
180
201
|
</template>
|
|
181
202
|
|
|
182
203
|
<script setup>
|
|
183
|
-
import { ref, computed, watch } from
|
|
184
|
-
import Modal from
|
|
185
|
-
import Switch from
|
|
186
|
-
import EyeToggle from
|
|
187
|
-
import
|
|
204
|
+
import { ref, computed, watch } from "vue";
|
|
205
|
+
import Modal from "@/components/ui/Modal.vue";
|
|
206
|
+
import Switch from "@/components/ui/controls/atomic/Switch.vue";
|
|
207
|
+
import EyeToggle from "@/components/ui/controls/EyeToggle.vue";
|
|
208
|
+
import InfoRow from "@/components/ui/InfoRow.vue";
|
|
209
|
+
import SectionCard from "@/components/ui/SectionCard.vue";
|
|
210
|
+
import { useUIStore } from "@/stores/ui";
|
|
211
|
+
import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
|
|
188
212
|
import {
|
|
189
213
|
EyeIcon,
|
|
190
214
|
StadiumIcon,
|
|
@@ -204,10 +228,13 @@ import {
|
|
|
204
228
|
SavingsIcon,
|
|
205
229
|
LoyaltyIcon,
|
|
206
230
|
SkiIcon,
|
|
207
|
-
SandclockIcon
|
|
208
|
-
|
|
231
|
+
SandclockIcon,
|
|
232
|
+
StatusIcon,
|
|
233
|
+
CopyIcon
|
|
234
|
+
} from "@/components/icons";
|
|
209
235
|
|
|
210
236
|
const ui = useUIStore();
|
|
237
|
+
const { copy } = useCopyToClipboard();
|
|
211
238
|
const showPassword = ref(false);
|
|
212
239
|
|
|
213
240
|
const props = defineProps({
|
|
@@ -217,35 +244,50 @@ const props = defineProps({
|
|
|
217
244
|
}
|
|
218
245
|
});
|
|
219
246
|
|
|
247
|
+
// Keep a local snapshot of task data that updates in real-time
|
|
248
|
+
const taskSnapshot = ref({ ...props.task });
|
|
249
|
+
|
|
250
|
+
// Watch for changes in the live task data from the store
|
|
251
|
+
const liveTask = computed(() => {
|
|
252
|
+
// Try to find the live task in the store
|
|
253
|
+
return ui.tasks?.[props.task.taskId];
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
watch(
|
|
257
|
+
liveTask,
|
|
258
|
+
(newTask) => {
|
|
259
|
+
if (newTask) {
|
|
260
|
+
// Task still exists - update snapshot with live data
|
|
261
|
+
taskSnapshot.value = { ...newTask };
|
|
262
|
+
}
|
|
263
|
+
// If newTask is undefined/null, task was deleted - keep last snapshot
|
|
264
|
+
},
|
|
265
|
+
{ deep: true, immediate: true }
|
|
266
|
+
);
|
|
220
267
|
|
|
221
268
|
const toggles = computed(() => ({
|
|
222
|
-
smartTimer:
|
|
223
|
-
loginAfterCart:
|
|
224
|
-
manual:
|
|
225
|
-
doNotPay:
|
|
226
|
-
presaleMode:
|
|
227
|
-
quickQueue:
|
|
228
|
-
agedAccount:
|
|
269
|
+
smartTimer: taskSnapshot.value.smartTimer,
|
|
270
|
+
loginAfterCart: taskSnapshot.value.loginAfterCart,
|
|
271
|
+
manual: taskSnapshot.value.manual,
|
|
272
|
+
doNotPay: taskSnapshot.value.doNotPay,
|
|
273
|
+
presaleMode: taskSnapshot.value.presaleMode,
|
|
274
|
+
quickQueue: taskSnapshot.value.quickQueue,
|
|
275
|
+
agedAccount: taskSnapshot.value.agedAccount
|
|
229
276
|
}));
|
|
230
277
|
|
|
231
|
-
const copy = (text) => {
|
|
232
|
-
navigator.clipboard.writeText(text);
|
|
233
|
-
ui.showSuccess(`Copied: ${text}`);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
278
|
const formatDate = (dateString) => {
|
|
237
|
-
if (!dateString) return
|
|
279
|
+
if (!dateString) return "";
|
|
238
280
|
try {
|
|
239
281
|
const date = new Date(dateString);
|
|
240
282
|
const options = {
|
|
241
|
-
month:
|
|
242
|
-
day:
|
|
243
|
-
year:
|
|
244
|
-
hour:
|
|
245
|
-
minute:
|
|
283
|
+
month: "short",
|
|
284
|
+
day: "numeric",
|
|
285
|
+
year: "numeric",
|
|
286
|
+
hour: "numeric",
|
|
287
|
+
minute: "2-digit",
|
|
246
288
|
hour12: true
|
|
247
289
|
};
|
|
248
|
-
return date.toLocaleString(
|
|
290
|
+
return date.toLocaleString("en-US", options).replace(",", "");
|
|
249
291
|
} catch {
|
|
250
292
|
return dateString;
|
|
251
293
|
}
|
|
@@ -258,103 +300,48 @@ const isTotalPrice = (line, index, lines) => {
|
|
|
258
300
|
if (!isLastLine) return false;
|
|
259
301
|
// Match currency symbols ($, €, £, etc.) OR currency codes (USD, EUR, AUD, etc.)
|
|
260
302
|
const totalPricePattern = /^([$€£¥₹₽¢]|[A-Z]{3})\s*[\d,]+\.?\d*$/;
|
|
261
|
-
return totalPricePattern.test(trimmed) && !trimmed.includes(
|
|
303
|
+
return totalPricePattern.test(trimmed) && !trimmed.includes("(");
|
|
262
304
|
};
|
|
263
305
|
|
|
264
306
|
const colorToClass = (color) => {
|
|
265
307
|
const colorMap = {
|
|
266
|
-
red:
|
|
267
|
-
green:
|
|
268
|
-
yellow:
|
|
269
|
-
blue:
|
|
308
|
+
red: "bg-red-400",
|
|
309
|
+
green: "bg-green-400",
|
|
310
|
+
yellow: "bg-yellow-400",
|
|
311
|
+
blue: "bg-blue-400"
|
|
270
312
|
};
|
|
271
|
-
return colorMap[color?.toLowerCase()] ||
|
|
313
|
+
return colorMap[color?.toLowerCase()] || "bg-gray-400";
|
|
272
314
|
};
|
|
273
315
|
</script>
|
|
274
316
|
|
|
275
317
|
<style lang="scss" scoped>
|
|
276
|
-
.section-card {
|
|
277
|
-
@apply rounded-lg border p-4;
|
|
278
|
-
background: oklch(0.2046 0 0);
|
|
279
|
-
border-color: oklch(0.2809 0 0);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.section-title {
|
|
283
|
-
@apply text-sm font-semibold mb-3;
|
|
284
|
-
color: oklch(0.90 0 0);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.info-row {
|
|
288
|
-
@apply flex items-center gap-3 px-3 py-2 rounded-lg;
|
|
289
|
-
background: oklch(0.2603 0 0);
|
|
290
|
-
border: 1px solid oklch(0.2809 0 0);
|
|
291
|
-
min-height: 40px;
|
|
292
|
-
|
|
293
|
-
&.copyable {
|
|
294
|
-
cursor: pointer;
|
|
295
|
-
|
|
296
|
-
&:hover {
|
|
297
|
-
border-color: oklch(0.72 0.15 145);
|
|
298
|
-
background: oklch(0.72 0.15 145 / 0.08);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
.info-icon {
|
|
304
|
-
@apply w-4 h-4 flex-shrink-0;
|
|
305
|
-
color: oklch(0.90 0 0) !important;
|
|
306
|
-
|
|
307
|
-
svg {
|
|
308
|
-
color: oklch(0.90 0 0) !important;
|
|
309
|
-
width: 16px !important;
|
|
310
|
-
height: 16px !important;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.info-label {
|
|
315
|
-
@apply text-xs font-medium flex-shrink-0;
|
|
316
|
-
color: oklch(0.65 0 0);
|
|
317
|
-
min-width: 100px;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.info-value {
|
|
321
|
-
@apply text-sm flex-1;
|
|
322
|
-
color: oklch(0.90 0 0);
|
|
323
|
-
overflow-wrap: break-word;
|
|
324
|
-
word-break: break-word;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
.copyable {
|
|
328
|
-
cursor: pointer;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
318
|
.toggle-item {
|
|
332
319
|
@apply flex flex-col items-center gap-2;
|
|
333
320
|
}
|
|
334
321
|
|
|
335
322
|
.toggle-label {
|
|
336
323
|
@apply flex items-center gap-2 text-xs;
|
|
337
|
-
color: oklch(0.
|
|
324
|
+
color: oklch(0.9 0 0);
|
|
338
325
|
|
|
339
326
|
svg {
|
|
340
|
-
color: oklch(0.
|
|
327
|
+
color: oklch(0.9 0 0) !important;
|
|
341
328
|
width: 16px !important;
|
|
342
329
|
height: 16px !important;
|
|
343
330
|
}
|
|
344
331
|
}
|
|
345
332
|
|
|
346
333
|
.status-indicator {
|
|
347
|
-
@apply
|
|
334
|
+
@apply h-2 w-2 flex-shrink-0 rounded-full;
|
|
348
335
|
}
|
|
349
336
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
font-size: 11px;
|
|
354
|
-
}
|
|
337
|
+
.info-icon {
|
|
338
|
+
@apply h-4 w-4 flex-shrink-0;
|
|
339
|
+
color: oklch(0.9 0 0) !important;
|
|
355
340
|
|
|
356
|
-
|
|
357
|
-
|
|
341
|
+
svg {
|
|
342
|
+
color: oklch(0.9 0 0) !important;
|
|
343
|
+
width: 16px !important;
|
|
344
|
+
height: 16px !important;
|
|
358
345
|
}
|
|
359
346
|
}
|
|
360
347
|
</style>
|
|
@@ -55,6 +55,7 @@ import CloseXIcon from "./CloseX.vue";
|
|
|
55
55
|
import CheckIcon from "./Check.vue";
|
|
56
56
|
import SpinnerIcon from "./Spinner.vue";
|
|
57
57
|
import EyeIcon from "./Eye.vue";
|
|
58
|
+
import CopyIcon from "./Copy.vue";
|
|
58
59
|
|
|
59
60
|
export {
|
|
60
61
|
EditIcon,
|
|
@@ -109,5 +110,6 @@ export {
|
|
|
109
110
|
CloseXIcon,
|
|
110
111
|
CheckIcon,
|
|
111
112
|
SpinnerIcon,
|
|
112
|
-
EyeIcon
|
|
113
|
+
EyeIcon,
|
|
114
|
+
CopyIcon
|
|
113
115
|
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ul class="mx-auto flex items-center justify-center rounded overflow-visible flex-wrap
|
|
3
|
+
bg-dark-400 border-2 border-dark-600 p-[3px] gap-0.5
|
|
4
|
+
md:p-0.5 md:gap-px md:rounded-md
|
|
5
|
+
lg:p-[3px] lg:gap-0.5 lg:rounded-lg lg:flex-nowrap
|
|
6
|
+
max-md:max-w-[44px] max-md:min-h-7 max-md:h-auto max-md:justify-start max-md:mx-0">
|
|
7
|
+
<slot />
|
|
8
|
+
</ul>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup>
|
|
12
|
+
// Simple wrapper component for action button groups
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<style lang="scss" scoped>
|
|
16
|
+
ul {
|
|
17
|
+
:deep(button) {
|
|
18
|
+
@apply relative flex items-center justify-center rounded border-0 outline-0 transition-all duration-150;
|
|
19
|
+
@apply bg-transparent text-light-300;
|
|
20
|
+
@apply w-7 h-7 rounded-md;
|
|
21
|
+
|
|
22
|
+
&:hover {
|
|
23
|
+
@apply bg-accent-green/15 text-white;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:active {
|
|
27
|
+
@apply bg-accent-green/25;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Tablet sizing
|
|
31
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
32
|
+
@apply w-[26px] h-[26px] rounded;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Desktop sizing
|
|
36
|
+
@media (min-width: 1024px) {
|
|
37
|
+
@apply w-7 h-7 rounded-md;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Mobile sizing
|
|
41
|
+
@media (max-width: 640px) {
|
|
42
|
+
@apply w-[18px] h-[18px] rounded-xs min-w-[18px] m-px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
:deep(svg),
|
|
47
|
+
:deep(img) {
|
|
48
|
+
@apply w-4 h-4 relative z-[1];
|
|
49
|
+
|
|
50
|
+
// Tablet sizing
|
|
51
|
+
@media (min-width: 768px) and (max-width: 1023px) {
|
|
52
|
+
@apply w-3.5 h-3.5;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Desktop sizing
|
|
56
|
+
@media (min-width: 1024px) {
|
|
57
|
+
@apply w-4 h-4;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Mobile sizing
|
|
61
|
+
@media (max-width: 640px) {
|
|
62
|
+
@apply w-[12px] h-[12px];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:deep(li) {
|
|
67
|
+
@apply flex m-0 p-0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
</style>
|