@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.
- package/backend/api.js +2 -3
- package/eslint.config.js +46 -0
- package/index.html +2 -1
- package/package.json +5 -2
- package/src/App.vue +70 -566
- package/src/assets/css/base/mixins.scss +72 -0
- package/src/assets/css/base/reset.scss +0 -2
- package/src/assets/css/base/scroll.scss +43 -36
- package/src/assets/css/base/typography.scss +9 -10
- package/src/assets/css/base/variables.scss +43 -0
- package/src/assets/css/components/accessibility.scss +37 -0
- package/src/assets/css/components/buttons.scss +61 -74
- package/src/assets/css/components/forms.scss +31 -32
- package/src/assets/css/components/headers.scss +13 -21
- package/src/assets/css/components/modals.scss +2 -2
- package/src/assets/css/components/search-groups.scss +28 -22
- package/src/assets/css/components/tables.scss +5 -7
- package/src/assets/css/components/toasts.scss +7 -7
- package/src/assets/css/components/utilities.scss +295 -0
- package/src/assets/css/main.scss +55 -139
- package/src/components/Auth/LoginForm.vue +7 -86
- package/src/components/Console/ConsoleToolbar.vue +123 -0
- package/src/components/Editors/Account/Account.vue +12 -12
- package/src/components/Editors/Account/AccountView.vue +38 -111
- package/src/components/Editors/Account/CreateAccount.vue +11 -61
- package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
- package/src/components/Editors/AdminFileEditor.vue +179 -0
- package/src/components/Editors/Profile/CreateProfile.vue +77 -150
- package/src/components/Editors/Profile/Profile.vue +20 -21
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
- package/src/components/Editors/Profile/ProfileView.vue +41 -116
- package/src/components/Editors/ProxyFileEditor.vue +86 -0
- package/src/components/Editors/TagLabel.vue +16 -55
- package/src/components/Editors/TagToggle.vue +20 -8
- package/src/components/Filter/Filter.vue +66 -79
- package/src/components/Filter/FilterPreview.vue +153 -135
- package/src/components/Filter/PriceSortToggle.vue +36 -43
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Table.vue +45 -51
- package/src/components/Tasks/CheckStock.vue +7 -16
- package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
- package/src/components/Tasks/Controls/MobileControls.vue +5 -20
- package/src/components/Tasks/CreateTaskAXS.vue +20 -118
- package/src/components/Tasks/CreateTaskTM.vue +33 -189
- package/src/components/Tasks/EventDetailRow.vue +21 -0
- package/src/components/Tasks/MassEdit.vue +6 -16
- package/src/components/Tasks/QuickSettings.vue +140 -216
- package/src/components/Tasks/ScrapeVenue.vue +4 -13
- package/src/components/Tasks/Stats.vue +20 -39
- package/src/components/Tasks/Task.vue +64 -270
- package/src/components/Tasks/TaskLabel.vue +9 -3
- package/src/components/Tasks/TaskView.vue +45 -64
- package/src/components/Tasks/Utilities.vue +10 -44
- package/src/components/Tasks/ViewTask.vue +23 -107
- package/src/components/icons/Close.vue +2 -8
- package/src/components/icons/Gear.vue +8 -8
- package/src/components/icons/Hash.vue +5 -0
- package/src/components/icons/Key.vue +2 -8
- package/src/components/icons/Pencil.vue +2 -8
- package/src/components/icons/Profile.vue +2 -8
- package/src/components/icons/Sell.vue +2 -8
- package/src/components/icons/Spinner.vue +4 -7
- package/src/components/icons/Wildcard.vue +2 -8
- package/src/components/icons/index.js +3 -5
- package/src/components/ui/ActionButtonGroup.vue +113 -52
- package/src/components/ui/BalanceIndicator.vue +60 -0
- package/src/components/ui/EmptyState.vue +24 -0
- package/src/components/ui/EnableDisableToggle.vue +23 -0
- package/src/components/ui/FormField.vue +49 -49
- package/src/components/ui/IconLabel.vue +23 -0
- package/src/components/ui/InfoRow.vue +21 -54
- package/src/components/ui/Modal.vue +161 -54
- package/src/components/ui/Navbar.vue +63 -44
- package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
- package/src/components/ui/ReconnectIndicator.vue +111 -124
- package/src/components/ui/SectionCard.vue +6 -14
- package/src/components/ui/Splash.vue +2 -10
- package/src/components/ui/StatusBadge.vue +26 -28
- package/src/components/ui/TaskToggle.vue +54 -0
- package/src/components/ui/controls/CountryChooser.vue +29 -66
- package/src/components/ui/controls/EyeToggle.vue +1 -1
- package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
- package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
- package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
- package/src/components/ui/controls/atomic/Switch.vue +21 -84
- package/src/composables/useCodeEditor.js +117 -0
- package/src/composables/useColorMapping.js +15 -0
- package/src/composables/useCopyToClipboard.js +1 -1
- package/src/composables/useDateFormatting.js +21 -0
- package/src/composables/useDeviceDetection.js +14 -0
- package/src/composables/useDropdownPosition.js +1 -4
- package/src/composables/useDynamicTableHeight.js +31 -0
- package/src/composables/useEnableDisable.js +6 -0
- package/src/composables/useFilterCSS.js +71 -0
- package/src/composables/useFormValidation.js +92 -0
- package/src/composables/useGetAllTags.js +9 -0
- package/src/composables/useIOSViewportHandling.js +76 -0
- package/src/composables/useNotchHandling.js +306 -0
- package/src/composables/useRowSelection.js +0 -3
- package/src/composables/useTableRender.js +23 -0
- package/src/composables/useTicketPricing.js +16 -0
- package/src/composables/useWindowDimensions.js +21 -0
- package/src/composables/useZoomPrevention.js +96 -0
- package/src/constants/tableLayout.js +14 -0
- package/src/libs/Filter.js +14 -20
- package/src/libs/panzoom.js +1 -5
- package/src/libs/utils/array.js +58 -0
- package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
- package/src/libs/utils/eventUrl.js +40 -0
- package/src/libs/utils/string.js +3 -0
- package/src/libs/utils/time.js +20 -0
- package/src/libs/utils/validation.js +64 -0
- package/src/main.js +0 -2
- package/src/stores/connection.js +1 -29
- package/src/stores/logger.js +6 -12
- package/src/stores/sampleData.js +1 -2
- package/src/stores/ui.js +80 -71
- package/src/utils/tableHelpers.js +1 -0
- package/src/views/Accounts.vue +19 -38
- package/src/views/Console.vue +74 -253
- package/src/views/Editor.vue +47 -1114
- package/src/views/FilterBuilder.vue +190 -461
- package/src/views/Login.vue +3 -28
- package/src/views/Profiles.vue +17 -32
- package/src/views/Tasks.vue +51 -38
- package/tailwind.config.js +82 -71
- package/workbox-config.cjs +47 -5
- package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
- package/exit +0 -209
- package/run +0 -177
- package/src/assets/css/base/color-fallbacks.scss +0 -10
- package/src/assets/img/background.svg.backup +0 -11
- package/src/components/icons/SquareCheck.vue +0 -18
- package/src/components/icons/SquareUncheck.vue +0 -18
- package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
- package/switch-branch.sh +0 -41
- /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="form-section">
|
|
3
3
|
<!-- Username -->
|
|
4
|
-
<div class="
|
|
5
|
-
<div class="flex items-center
|
|
4
|
+
<div class="form-field-labeled mb-3">
|
|
5
|
+
<div class="flex-gap-2 items-center">
|
|
6
6
|
<ProfileIcon class="w-4 h-4" />
|
|
7
7
|
<span class="text-light-300 font-medium text-sm">Username</span>
|
|
8
8
|
</div>
|
|
9
9
|
<div class="flex-1 flex items-center">
|
|
10
|
-
<input
|
|
11
|
-
type="text"
|
|
12
|
-
class="login-input"
|
|
13
|
-
v-model="user"
|
|
14
|
-
autocapitalize="off"
|
|
15
|
-
autocorrect="off"
|
|
16
|
-
autocomplete="username"
|
|
17
|
-
/>
|
|
10
|
+
<input type="text" class="form-input-minimal" v-model="user" autocapitalize="off" autocorrect="off" autocomplete="username" aria-label="Username" />
|
|
18
11
|
</div>
|
|
19
12
|
</div>
|
|
20
13
|
<!-- Password -->
|
|
21
|
-
<div class="
|
|
22
|
-
<div class="flex items-center
|
|
14
|
+
<div class="form-field-labeled mb-4">
|
|
15
|
+
<div class="flex-gap-2 items-center">
|
|
23
16
|
<KeyIcon class="w-4 h-4" />
|
|
24
17
|
<span class="text-light-300 font-medium text-sm">Password</span>
|
|
25
18
|
</div>
|
|
26
19
|
<div class="flex-1 flex items-center">
|
|
27
|
-
<input
|
|
28
|
-
type="password"
|
|
29
|
-
class="login-input"
|
|
30
|
-
v-model="password"
|
|
31
|
-
/>
|
|
20
|
+
<input type="password" class="form-input-minimal" v-model="password" aria-label="Password" />
|
|
32
21
|
</div>
|
|
33
22
|
</div>
|
|
34
|
-
<button
|
|
35
|
-
class="login-btn mt-6 mx-auto"
|
|
36
|
-
@click="login()"
|
|
37
|
-
:disabled="buttonDisabled"
|
|
38
|
-
>
|
|
23
|
+
<button class="login-btn w-48 mt-6 mx-auto" @click="login()" :disabled="buttonDisabled">
|
|
39
24
|
<span v-if="!buttonDisabled">Login</span>
|
|
40
25
|
<svg v-if="!buttonDisabled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="w-5 h-5">
|
|
41
26
|
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M13.8 12H3"/>
|
|
@@ -68,67 +53,3 @@ async function login() {
|
|
|
68
53
|
buttonDisabled.value = false;
|
|
69
54
|
}
|
|
70
55
|
</script>
|
|
71
|
-
|
|
72
|
-
<style lang="scss" scoped>
|
|
73
|
-
.login-btn {
|
|
74
|
-
@apply flex items-center justify-center gap-2 rounded-lg transition-all duration-150;
|
|
75
|
-
background: oklch(0.72 0.15 145);
|
|
76
|
-
border: 2px solid oklch(0.72 0.15 145);
|
|
77
|
-
color: oklch(1 0 0);
|
|
78
|
-
height: 3rem;
|
|
79
|
-
width: 12rem;
|
|
80
|
-
font-size: 0.9375rem;
|
|
81
|
-
font-weight: 600;
|
|
82
|
-
letter-spacing: 0.05em;
|
|
83
|
-
text-transform: uppercase;
|
|
84
|
-
|
|
85
|
-
&:hover:not(:disabled) {
|
|
86
|
-
background: oklch(0.68 0.15 145);
|
|
87
|
-
border-color: oklch(0.68 0.15 145);
|
|
88
|
-
transform: translateY(-1px);
|
|
89
|
-
box-shadow: 0 4px 12px oklch(0.72 0.15 145 / 0.3);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
&:active:not(:disabled) {
|
|
93
|
-
transform: translateY(0);
|
|
94
|
-
box-shadow: 0 2px 4px oklch(0.72 0.15 145 / 0.2);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
&:disabled {
|
|
98
|
-
opacity: 0.7;
|
|
99
|
-
cursor: not-allowed;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.input-container {
|
|
104
|
-
@apply text-white bg-dark-500 px-3 rounded-lg border-2 border-dark-550 flex items-center justify-between h-11;
|
|
105
|
-
overflow: visible;
|
|
106
|
-
transition: border-color 0.15s ease;
|
|
107
|
-
|
|
108
|
-
&:hover {
|
|
109
|
-
border-color: oklch(0.30 0 0);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
&:focus-within {
|
|
113
|
-
border-color: oklch(0.72 0.15 145) !important;
|
|
114
|
-
outline: 1px solid oklch(0.72 0.15 145);
|
|
115
|
-
outline-offset: 0;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.login-input {
|
|
120
|
-
@apply w-full h-full text-sm text-white bg-transparent border-0 outline-none px-2 py-1;
|
|
121
|
-
|
|
122
|
-
&:focus {
|
|
123
|
-
@apply outline-none border-0 shadow-none bg-transparent;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
&:hover:not(:focus) {
|
|
127
|
-
background: transparent;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
&::placeholder {
|
|
131
|
-
color: oklch(0.50 0 0);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div class="mb-3 flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
|
4
|
+
<div class="flex flex-col gap-3 md:flex-1 md:flex-row md:items-center">
|
|
5
|
+
<div class="w-full md:w-64">
|
|
6
|
+
<Dropdown
|
|
7
|
+
class="console-dropdown input-default w-full border-2 border-dark-550 bg-dark-500"
|
|
8
|
+
rightAmount="right-2"
|
|
9
|
+
default="All logs"
|
|
10
|
+
:allowDefault="true"
|
|
11
|
+
:value="currentTaskLog"
|
|
12
|
+
:onClick="(f) => $emit('update:currentTaskLog', f.split(' ')[0])"
|
|
13
|
+
:options="Object.entries(taskLogMapping).map(([k, v]) => `${k} (${v.length})`).sort((a, b) => a.localeCompare(b))" />
|
|
14
|
+
</div>
|
|
15
|
+
<div class="flex flex-1 items-center gap-2">
|
|
16
|
+
<div class="input-default flex flex-1 items-center md:max-w-64">
|
|
17
|
+
<input
|
|
18
|
+
:value="searchQuery"
|
|
19
|
+
@input="$emit('update:searchQuery', $event.target.value)"
|
|
20
|
+
type="text"
|
|
21
|
+
placeholder="Search logs..."
|
|
22
|
+
aria-label="Search logs"
|
|
23
|
+
class="transparent-input" />
|
|
24
|
+
<span v-if="searchQuery" class="ml-2 text-xs text-light-500">{{ filteredCount }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
<button
|
|
27
|
+
class="console-scroll-btn md:hidden"
|
|
28
|
+
@mousedown="$emit('scroll', 'up')"
|
|
29
|
+
@mouseup="$emit('scroll-stop')"
|
|
30
|
+
@mouseleave="$emit('scroll-stop')"
|
|
31
|
+
@touchstart="$emit('scroll', 'up')"
|
|
32
|
+
@touchend="$emit('scroll-stop')"
|
|
33
|
+
aria-label="Scroll up">
|
|
34
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
class="console-scroll-btn md:hidden"
|
|
38
|
+
@mousedown="$emit('scroll', 'down')"
|
|
39
|
+
@mouseup="$emit('scroll-stop')"
|
|
40
|
+
@mouseleave="$emit('scroll-stop')"
|
|
41
|
+
@touchstart="$emit('scroll', 'down')"
|
|
42
|
+
@touchend="$emit('scroll-stop')">
|
|
43
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="flex hidden items-center gap-3 md:flex">
|
|
48
|
+
<div class="hidden items-center gap-3 md:flex">
|
|
49
|
+
<button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
50
|
+
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
51
|
+
<Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
|
|
52
|
+
</button>
|
|
53
|
+
<button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
54
|
+
<h3 class="text-sm text-white">Auto</h3>
|
|
55
|
+
<Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
|
|
56
|
+
<div
|
|
57
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
58
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
59
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
60
|
+
</button>
|
|
61
|
+
</div>
|
|
62
|
+
<button
|
|
63
|
+
class="hidden size-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
|
|
64
|
+
@mousedown="$emit('scroll', 'up')"
|
|
65
|
+
@mouseup="$emit('scroll-stop')"
|
|
66
|
+
@mouseleave="$emit('scroll-stop')"
|
|
67
|
+
@touchstart="$emit('scroll', 'up')"
|
|
68
|
+
@touchend="$emit('scroll-stop')">
|
|
69
|
+
<UpIcon class="pointer-events-none h-5 w-5" />
|
|
70
|
+
</button>
|
|
71
|
+
<button
|
|
72
|
+
class="hidden size-10 items-center justify-center rounded border border-dark-650 bg-dark-400 shadow-sm transition-colors duration-150 hover:bg-dark-300 active:bg-dark-200 md:flex"
|
|
73
|
+
@mousedown="$emit('scroll', 'down')"
|
|
74
|
+
@mouseup="$emit('scroll-stop')"
|
|
75
|
+
@mouseleave="$emit('scroll-stop')"
|
|
76
|
+
@touchstart="$emit('scroll', 'down')"
|
|
77
|
+
@touchend="$emit('scroll-stop')">
|
|
78
|
+
<DownIcon class="pointer-events-none h-5 w-5" />
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="mb-6 mt-4 flex justify-between md:hidden mobile-portrait:mb-16 mobile-portrait:mt-6">
|
|
83
|
+
<button class="flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
84
|
+
<h3 class="text-sm text-white">Hide Monitors</h3>
|
|
85
|
+
<Switch class="scale-75" :model-value="filteredLogs" @update:model-value="$emit('update:filteredLogs', $event)" />
|
|
86
|
+
</button>
|
|
87
|
+
<button class="relative flex h-10 items-center justify-center gap-3 rounded border border-dark-650 bg-dark-400 px-2 shadow-sm">
|
|
88
|
+
<h3 class="text-sm text-white">Auto</h3>
|
|
89
|
+
<Switch class="scale-75" :model-value="autoscrollToggled" @update:model-value="$emit('update:autoscrollToggled', $event); $emit('autoscroll-toggle')" />
|
|
90
|
+
<div
|
|
91
|
+
v-if="userScrolledUp && autoscrollToggled"
|
|
92
|
+
class="absolute -right-1 -top-1 h-2 w-2 animate-pulse rounded-full bg-yellow-500"
|
|
93
|
+
title="Autoscroll paused - scroll to bottom to resume"></div>
|
|
94
|
+
</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup>
|
|
100
|
+
import { DownIcon, UpIcon } from '@/components/icons';
|
|
101
|
+
import Switch from '@/components/ui/controls/atomic/Switch.vue';
|
|
102
|
+
import Dropdown from '@/components/ui/controls/atomic/Dropdown.vue';
|
|
103
|
+
|
|
104
|
+
defineProps({
|
|
105
|
+
currentTaskLog: String,
|
|
106
|
+
taskLogMapping: Object,
|
|
107
|
+
searchQuery: String,
|
|
108
|
+
filteredLogs: Boolean,
|
|
109
|
+
autoscrollToggled: Boolean,
|
|
110
|
+
userScrolledUp: Boolean,
|
|
111
|
+
filteredCount: String
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
defineEmits([
|
|
115
|
+
'update:currentTaskLog',
|
|
116
|
+
'update:searchQuery',
|
|
117
|
+
'update:filteredLogs',
|
|
118
|
+
'update:autoscrollToggled',
|
|
119
|
+
'scroll',
|
|
120
|
+
'scroll-stop',
|
|
121
|
+
'autoscroll-toggle'
|
|
122
|
+
]);
|
|
123
|
+
</script>
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
class="ml-0 mr-4"
|
|
12
12
|
:toggled="props.account.selected"
|
|
13
13
|
@valueUpdate="ui.toggleAccountSelected(props.account.id)" />
|
|
14
|
-
<h4 class="mx-auto text-white" @click="copy(props.account.email, 'Copied email')">
|
|
14
|
+
<h4 class="mx-auto text-center text-white" @click="copy(props.account.email, 'Copied email')">
|
|
15
15
|
{{ props.account.email }}
|
|
16
16
|
</h4>
|
|
17
17
|
</div>
|
|
18
18
|
<div class="col-span-2 hidden md:block" @click="copy(props.account.password, 'Copied password')">
|
|
19
|
-
<h4 class="text-white">
|
|
19
|
+
<h4 class="text-center text-white">
|
|
20
20
|
{{ props.account.privacy ? "•".repeat(props.account.password.length) : props.account.password }}
|
|
21
21
|
</h4>
|
|
22
22
|
</div>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
27
|
<div class="col-span-1 hidden lg:block">
|
|
28
|
-
<h4 class="flex justify-center gap-1 text-white">
|
|
28
|
+
<h4 class="flex justify-center gap-1 text-center text-white">
|
|
29
29
|
<TagLabel v-for="tag in props.account.tags" :key="tag" :text="tag" />
|
|
30
30
|
</h4>
|
|
31
31
|
</div>
|
|
@@ -39,26 +39,21 @@
|
|
|
39
39
|
</li>
|
|
40
40
|
<li v-if="props.account.enabled">
|
|
41
41
|
<button @click="disable">
|
|
42
|
-
<img class="
|
|
42
|
+
<img class="icon-md" src="/img/controls/disable.svg" />
|
|
43
43
|
</button>
|
|
44
44
|
</li>
|
|
45
45
|
<li v-else>
|
|
46
46
|
<button @click="enable">
|
|
47
|
-
<img class="
|
|
47
|
+
<img class="icon-md" src="/img/controls/enable.svg" />
|
|
48
48
|
</button>
|
|
49
49
|
</li>
|
|
50
50
|
</ActionButtonGroup>
|
|
51
51
|
</div>
|
|
52
52
|
</Row>
|
|
53
53
|
</template>
|
|
54
|
-
<style lang="scss" scoped>
|
|
55
|
-
h4 {
|
|
56
|
-
@apply text-center;
|
|
57
|
-
}
|
|
58
|
-
</style>
|
|
59
54
|
<script setup>
|
|
60
55
|
import { Row } from "@/components/Table";
|
|
61
|
-
import {
|
|
56
|
+
import { EditIcon } from "@/components/icons";
|
|
62
57
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
63
58
|
import StatusBadge from "@/components/ui/StatusBadge.vue";
|
|
64
59
|
import ActionButtonGroup from "@/components/ui/ActionButtonGroup.vue";
|
|
@@ -66,12 +61,17 @@ import { useUIStore } from "@/stores/ui";
|
|
|
66
61
|
import TagLabel from "@/components/Editors/TagLabel.vue";
|
|
67
62
|
import { useRowSelection } from "@/composables/useRowSelection";
|
|
68
63
|
import { useCopyToClipboard } from "@/composables/useCopyToClipboard";
|
|
64
|
+
import { computed } from "vue";
|
|
65
|
+
import { useEnableDisable } from "@/composables/useEnableDisable";
|
|
69
66
|
|
|
70
67
|
const ui = useUIStore();
|
|
71
68
|
const { copy } = useCopyToClipboard();
|
|
72
69
|
|
|
73
70
|
const props = defineProps({
|
|
74
|
-
account: {
|
|
71
|
+
account: {
|
|
72
|
+
type: Object,
|
|
73
|
+
required: true
|
|
74
|
+
}
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
const enable = async () => await ui.addAccount({ ...props.account, enabled: true });
|
|
@@ -1,152 +1,79 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<Header class="sticky top-0 z-10 grid-cols-5
|
|
4
|
-
<div class="col-span-3 flex lg:col-span-2">
|
|
2
|
+
<div class="table-component relative box-border flex flex-col rounded-lg bg-dark-500 bg-clip-padding overflow-hidden shadow-sm">
|
|
3
|
+
<Header class="sticky top-0 z-10 grid-cols-5 text-center md:grid-cols-7">
|
|
4
|
+
<div class="col-span-3 flex items-center justify-start lg:col-span-2">
|
|
5
5
|
<Checkbox
|
|
6
|
-
class="mr-
|
|
6
|
+
class="ml-2 mr-4 flex-shrink-0"
|
|
7
7
|
:toggled="ui.mainCheckbox.accounts"
|
|
8
8
|
@valueUpdate="ui.toggleMainCheckbox('accounts')"
|
|
9
9
|
:isHeader="true" />
|
|
10
|
-
<div class="mx-auto flex items-center" @click="ui.toggleSort('eventId')">
|
|
11
|
-
<MailIcon class="mr-0
|
|
12
|
-
<h4 class="hidden md:flex">Email</h4>
|
|
10
|
+
<div class="mx-auto flex cursor-pointer items-center" @click="ui.toggleSort('eventId')">
|
|
11
|
+
<MailIcon class="mr-0 icon-md md:mr-3" />
|
|
12
|
+
<h4 class="hidden text-white md:flex">Email</h4>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
15
15
|
<div class="col-span-2 hidden items-center justify-center md:flex" v-once>
|
|
16
|
-
<KeyIcon class="mr-0
|
|
17
|
-
<h4 class="hidden md:flex">Password</h4>
|
|
16
|
+
<KeyIcon class="mr-0 icon-md md:mr-3" />
|
|
17
|
+
<h4 class="hidden text-white md:flex">Password</h4>
|
|
18
18
|
</div>
|
|
19
|
-
<div class="
|
|
20
|
-
<CheckmarkIcon class="mr-0
|
|
21
|
-
<h4 class="hidden md:flex">Enabled</h4>
|
|
19
|
+
<div class="grid-cell-center" v-once>
|
|
20
|
+
<CheckmarkIcon class="mr-0 icon-md md:mr-3" />
|
|
21
|
+
<h4 class="hidden text-white md:flex">Enabled</h4>
|
|
22
22
|
</div>
|
|
23
23
|
<div class="col-span-1 hidden items-center justify-center lg:flex" v-once>
|
|
24
|
-
<TicketIcon class="mr-0
|
|
25
|
-
<h4 class="hidden md:flex">Tags</h4>
|
|
24
|
+
<TicketIcon class="mr-0 icon-md md:mr-3" />
|
|
25
|
+
<h4 class="hidden text-white md:flex">Tags</h4>
|
|
26
26
|
</div>
|
|
27
|
-
<div class="
|
|
28
|
-
<ClickIcon class="mr-0
|
|
29
|
-
<h4 class="hidden md:flex">Actions</h4>
|
|
27
|
+
<div class="grid-cell-center" v-once>
|
|
28
|
+
<ClickIcon class="mr-0 icon-md md:mr-3" />
|
|
29
|
+
<h4 class="hidden text-white md:flex">Actions</h4>
|
|
30
30
|
</div>
|
|
31
31
|
</Header>
|
|
32
32
|
<div
|
|
33
33
|
v-if="toRender.length != 0"
|
|
34
|
-
class="hidden-scrollbars
|
|
34
|
+
class="hidden-scrollbars flex flex-col divide-y divide-dark-650 overflow-y-auto overflow-x-hidden transition-colors duration-150 table-scroll"
|
|
35
35
|
:style="{ maxHeight: dynamicTableHeight }">
|
|
36
|
-
<div
|
|
36
|
+
<div
|
|
37
|
+
v-for="(account, i) in toRender"
|
|
38
|
+
:key="account.id || account.index"
|
|
39
|
+
class="min-h-16 flex-shrink-0 hover:bg-dark-550">
|
|
37
40
|
<Account
|
|
38
|
-
:class="i
|
|
41
|
+
:class="getRowClass(i)"
|
|
39
42
|
:account="account" />
|
|
40
43
|
</div>
|
|
41
44
|
</div>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
<p class="text-sm text-light-400">No accounts found</p>
|
|
45
|
-
<p class="mt-1 text-xs text-light-500">Create accounts to get started</p>
|
|
46
|
-
</div>
|
|
47
|
-
</Table>
|
|
45
|
+
<EmptyState v-else :icon="MailIcon" message="No accounts found" subtitle="Create accounts to get started" />
|
|
46
|
+
</div>
|
|
48
47
|
</template>
|
|
49
|
-
<style lang="scss" scoped>
|
|
50
|
-
.account-row-container {
|
|
51
|
-
min-height: 64px;
|
|
52
|
-
flex-shrink: 0;
|
|
53
|
-
transition: background-color 0.15s ease;
|
|
54
|
-
|
|
55
|
-
&:hover {
|
|
56
|
-
@apply bg-dark-550 !important;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
h4 {
|
|
61
|
-
@apply text-white;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.stop-pan {
|
|
65
|
-
touch-action: pan-y pan-up pan-down;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.empty-state {
|
|
69
|
-
font-size: 14px;
|
|
70
|
-
font-weight: 500;
|
|
71
|
-
}
|
|
72
|
-
</style>
|
|
73
48
|
<script setup>
|
|
74
|
-
import {
|
|
49
|
+
import { Header } from "@/components/Table";
|
|
75
50
|
import {
|
|
76
|
-
EventIcon,
|
|
77
51
|
TicketIcon,
|
|
78
|
-
StatusIcon,
|
|
79
52
|
ClickIcon,
|
|
80
|
-
DownIcon,
|
|
81
53
|
MailIcon,
|
|
82
54
|
KeyIcon,
|
|
83
55
|
CheckmarkIcon
|
|
84
56
|
} from "@/components/icons";
|
|
85
57
|
import Account from "./Account.vue";
|
|
86
58
|
import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
|
|
59
|
+
import EmptyState from "@/components/ui/EmptyState.vue";
|
|
87
60
|
import { useUIStore } from "@/stores/ui";
|
|
88
|
-
import {
|
|
61
|
+
import { useDynamicTableHeight } from "@/composables/useDynamicTableHeight";
|
|
62
|
+
import { computed } from "vue";
|
|
63
|
+
import { useTableRender } from "@/composables/useTableRender";
|
|
64
|
+
import { getRowClass } from "@/utils/tableHelpers";
|
|
89
65
|
|
|
90
66
|
const props = defineProps({
|
|
91
|
-
accounts: {
|
|
67
|
+
accounts: {
|
|
68
|
+
type: Object,
|
|
69
|
+
required: true
|
|
70
|
+
}
|
|
92
71
|
});
|
|
93
72
|
const ui = useUIStore();
|
|
94
73
|
|
|
95
|
-
const
|
|
96
|
-
const toRender = computed(() => {
|
|
97
|
-
let c = 0;
|
|
98
|
-
const rendered = props.accounts.map((t) => ({ ...t, index: c++ }));
|
|
99
|
-
|
|
100
|
-
// Initialize reactive refs for click tracking
|
|
101
|
-
rendered.forEach((t) => {
|
|
102
|
-
if (t.id && !(t.id in i.value)) {
|
|
103
|
-
i.value[t.id] = 0;
|
|
104
|
-
}
|
|
105
|
-
if (!(t.index in i.value)) {
|
|
106
|
-
i.value[t.index] = 0;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
return rendered;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// Dynamic height calculation for perfect item fitting
|
|
114
|
-
const windowHeight = ref(window.innerHeight);
|
|
115
|
-
const windowWidth = ref(window.innerWidth);
|
|
116
|
-
|
|
117
|
-
const updateDimensions = () => {
|
|
118
|
-
windowHeight.value = window.innerHeight;
|
|
119
|
-
windowWidth.value = window.innerWidth;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
onMounted(() => {
|
|
123
|
-
window.addEventListener("resize", updateDimensions);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
onUnmounted(() => {
|
|
127
|
-
window.removeEventListener("resize", updateDimensions);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const dynamicTableHeight = computed(() => {
|
|
131
|
-
// Calculate available space for accounts table with conservative buffer
|
|
132
|
-
const headerHeight = 60; // Header + navbar
|
|
133
|
-
const titleHeight = 50; // Accounts title and controls
|
|
134
|
-
const searchHeight = 50; // Search and filter controls
|
|
135
|
-
const margins = windowWidth.value >= 1024 ? 40 : 25;
|
|
136
|
-
const bufferSpace = 50; // Conservative buffer to prevent partial items
|
|
137
|
-
|
|
138
|
-
const totalUsedSpace = headerHeight + titleHeight + searchHeight + margins + bufferSpace;
|
|
139
|
-
const availableHeight = windowHeight.value - totalUsedSpace;
|
|
74
|
+
const { toRender } = useTableRender(computed(() => props.accounts));
|
|
140
75
|
|
|
141
|
-
|
|
142
|
-
const rowHeight = 64;
|
|
143
|
-
const minRowsToShow = 2;
|
|
144
|
-
const minHeight = minRowsToShow * rowHeight;
|
|
76
|
+
import { TABLE_LAYOUT } from "@/constants/tableLayout";
|
|
145
77
|
|
|
146
|
-
|
|
147
|
-
const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
|
|
148
|
-
const exactHeight = maxCompleteRows * rowHeight;
|
|
149
|
-
|
|
150
|
-
return exactHeight + "px";
|
|
151
|
-
});
|
|
78
|
+
const { dynamicTableHeight } = useDynamicTableHeight(TABLE_LAYOUT.ACCOUNTS);
|
|
152
79
|
</script>
|
|
@@ -9,11 +9,7 @@
|
|
|
9
9
|
<div>
|
|
10
10
|
<div class="my-3 grid grid-cols-12 gap-3">
|
|
11
11
|
<!-- Account tag -->
|
|
12
|
-
<
|
|
13
|
-
<label class="label-override mb-2">
|
|
14
|
-
Account Tag
|
|
15
|
-
<TagIcon />
|
|
16
|
-
</label>
|
|
12
|
+
<FormField label="Account Tag" :icon="TagIcon" z-index="0" class="col-span-12 md:col-span-4" noWrapper>
|
|
17
13
|
<Dropdown
|
|
18
14
|
:class="`input-default dropdown w-full p-4`"
|
|
19
15
|
:default="ui.profile.tags[0]"
|
|
@@ -22,10 +18,10 @@
|
|
|
22
18
|
:capitalize="true"
|
|
23
19
|
:allowDefault="false"
|
|
24
20
|
:chosen="account.tag" />
|
|
25
|
-
</
|
|
21
|
+
</FormField>
|
|
26
22
|
|
|
27
23
|
<!-- Email -->
|
|
28
|
-
<FormField label="Email" :icon="MailIcon" required :error="errors.includes('email')" z-index="0" class="col-span-8">
|
|
24
|
+
<FormField label="Email" :icon="MailIcon" required :error="errors.includes('email')" z-index="0" class="col-span-12 md:col-span-8">
|
|
29
25
|
<input
|
|
30
26
|
placeholder="email@example.com"
|
|
31
27
|
type="email"
|
|
@@ -54,47 +50,15 @@
|
|
|
54
50
|
</div>
|
|
55
51
|
|
|
56
52
|
<!-- Readonly fields when editing -->
|
|
57
|
-
<
|
|
58
|
-
<div v-if="ui.currentlyEditing.tags && ui.currentlyEditing.tags.length > 0" class="col-span-6">
|
|
59
|
-
<label class="label-override mb-2">Tags</label>
|
|
60
|
-
<div class="flex gap-2 flex-wrap">
|
|
61
|
-
<TagLabel v-for="tag in ui.currentlyEditing.tags" :key="tag" :text="tag" />
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
<div class="col-span-6">
|
|
65
|
-
<label class="label-override mb-2">Status</label>
|
|
66
|
-
<div class="flex items-center gap-3 h-10">
|
|
67
|
-
<StatusBadge :enabled="ui.currentlyEditing.enabled" size="large" />
|
|
68
|
-
<span class="text-sm font-medium" :class="ui.currentlyEditing.enabled ? 'text-green-400' : 'text-red-400'">
|
|
69
|
-
{{ ui.currentlyEditing.enabled ? 'Enabled' : 'Disabled' }}
|
|
70
|
-
</span>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</div>
|
|
53
|
+
<ReadonlyFieldsSection v-if="ui.currentlyEditing?.email" :data="ui.currentlyEditing" />
|
|
74
54
|
</div>
|
|
75
55
|
|
|
76
|
-
<button
|
|
77
|
-
class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
|
|
78
|
-
@click="done()">
|
|
56
|
+
<button class="btn-modal ml-auto mt-4 w-48" @click="done()">
|
|
79
57
|
Save
|
|
80
|
-
<EditIcon />
|
|
58
|
+
<EditIcon class="ml-2" />
|
|
81
59
|
</button>
|
|
82
60
|
</Modal>
|
|
83
61
|
</template>
|
|
84
|
-
<style lang="scss" scoped>
|
|
85
|
-
.label-override {
|
|
86
|
-
@apply flex items-center;
|
|
87
|
-
color: #e1e1e4 !important;
|
|
88
|
-
|
|
89
|
-
svg {
|
|
90
|
-
@apply ml-2;
|
|
91
|
-
|
|
92
|
-
path {
|
|
93
|
-
fill: #e1e1e4 !important;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
</style>
|
|
98
62
|
<script setup>
|
|
99
63
|
import Modal from "@/components/ui/Modal.vue";
|
|
100
64
|
import FormField from "@/components/ui/FormField.vue";
|
|
@@ -102,18 +66,13 @@ import {
|
|
|
102
66
|
EditIcon,
|
|
103
67
|
MailIcon,
|
|
104
68
|
KeyIcon,
|
|
105
|
-
|
|
106
|
-
TimerIcon,
|
|
107
|
-
SandclockIcon,
|
|
108
|
-
TagIcon,
|
|
109
|
-
ScannerIcon
|
|
69
|
+
TagIcon
|
|
110
70
|
} from "@/components/icons";
|
|
111
71
|
import { useUIStore } from "@/stores/ui";
|
|
112
72
|
import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
|
|
113
|
-
import
|
|
114
|
-
import TagLabel from "@/components/Editors/TagLabel.vue";
|
|
115
|
-
|
|
73
|
+
import ReadonlyFieldsSection from "@/components/ui/ReadonlyFieldsSection.vue";
|
|
116
74
|
import { ref } from "vue";
|
|
75
|
+
import { useFormValidation } from "@/composables/useFormValidation";
|
|
117
76
|
|
|
118
77
|
const ui = useUIStore();
|
|
119
78
|
const account = ref({
|
|
@@ -124,19 +83,10 @@ const account = ref({
|
|
|
124
83
|
|
|
125
84
|
if (ui.currentlyEditing?.email) account.value = ui.currentlyEditing;
|
|
126
85
|
|
|
127
|
-
const errors =
|
|
128
|
-
|
|
129
|
-
const validate = (p) => {
|
|
130
|
-
errors.value = [];
|
|
131
|
-
|
|
132
|
-
if (!p.email.includes("@")) errors.value.push("email");
|
|
133
|
-
if (p.password.length < 5) errors.value.push("password");
|
|
134
|
-
|
|
135
|
-
return errors.value.length === 0;
|
|
136
|
-
};
|
|
86
|
+
const { errors, validateAccount } = useFormValidation();
|
|
137
87
|
|
|
138
88
|
function done() {
|
|
139
|
-
if (!
|
|
89
|
+
if (!validateAccount(account.value)) return;
|
|
140
90
|
ui.toggleModal("");
|
|
141
91
|
ui.addAccount(account.value);
|
|
142
92
|
}
|