@necrolab/dashboard 0.5.15 → 0.5.16

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 (121) hide show
  1. package/backend/api.js +2 -3
  2. package/eslint.config.js +46 -0
  3. package/index.html +2 -1
  4. package/package.json +5 -2
  5. package/src/App.vue +140 -170
  6. package/src/assets/css/base/mixins.scss +72 -0
  7. package/src/assets/css/base/reset.scss +0 -2
  8. package/src/assets/css/base/scroll.scss +43 -36
  9. package/src/assets/css/base/typography.scss +9 -10
  10. package/src/assets/css/base/variables.scss +43 -0
  11. package/src/assets/css/components/accessibility.scss +37 -0
  12. package/src/assets/css/components/buttons.scss +58 -15
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +12 -20
  15. package/src/assets/css/components/modals.scss +2 -2
  16. package/src/assets/css/components/search-groups.scss +28 -22
  17. package/src/assets/css/components/tables.scss +5 -7
  18. package/src/assets/css/components/toasts.scss +7 -7
  19. package/src/assets/css/components/utilities.scss +220 -0
  20. package/src/assets/css/main.scss +66 -77
  21. package/src/components/Auth/LoginForm.vue +5 -84
  22. package/src/components/Editors/Account/Account.vue +8 -10
  23. package/src/components/Editors/Account/AccountCreator.vue +28 -59
  24. package/src/components/Editors/Account/AccountView.vue +38 -86
  25. package/src/components/Editors/Account/CreateAccount.vue +8 -50
  26. package/src/components/Editors/Profile/CreateProfile.vue +74 -131
  27. package/src/components/Editors/Profile/Profile.vue +15 -17
  28. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  29. package/src/components/Editors/Profile/ProfileView.vue +46 -96
  30. package/src/components/Editors/TagLabel.vue +16 -55
  31. package/src/components/Editors/TagToggle.vue +20 -8
  32. package/src/components/Filter/Filter.vue +62 -75
  33. package/src/components/Filter/FilterPreview.vue +161 -135
  34. package/src/components/Filter/PriceSortToggle.vue +36 -43
  35. package/src/components/Table/Header.vue +1 -1
  36. package/src/components/Table/Table.vue +45 -51
  37. package/src/components/Tasks/CheckStock.vue +7 -16
  38. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  39. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  40. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  41. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  42. package/src/components/Tasks/EventDetailRow.vue +21 -0
  43. package/src/components/Tasks/MassEdit.vue +6 -16
  44. package/src/components/Tasks/QuickSettings.vue +140 -216
  45. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  46. package/src/components/Tasks/Stats.vue +19 -38
  47. package/src/components/Tasks/Task.vue +65 -268
  48. package/src/components/Tasks/TaskLabel.vue +9 -3
  49. package/src/components/Tasks/TaskView.vue +43 -63
  50. package/src/components/Tasks/Utilities.vue +10 -44
  51. package/src/components/Tasks/ViewTask.vue +23 -107
  52. package/src/components/icons/Close.vue +2 -8
  53. package/src/components/icons/Gear.vue +8 -8
  54. package/src/components/icons/Hash.vue +5 -0
  55. package/src/components/icons/Key.vue +2 -8
  56. package/src/components/icons/Pencil.vue +2 -8
  57. package/src/components/icons/Profile.vue +2 -8
  58. package/src/components/icons/Sell.vue +2 -8
  59. package/src/components/icons/Spinner.vue +4 -7
  60. package/src/components/icons/SquareCheck.vue +2 -8
  61. package/src/components/icons/SquareUncheck.vue +2 -8
  62. package/src/components/icons/Wildcard.vue +2 -8
  63. package/src/components/icons/index.js +3 -1
  64. package/src/components/ui/ActionButtonGroup.vue +113 -52
  65. package/src/components/ui/BalanceIndicator.vue +60 -0
  66. package/src/components/ui/EmptyState.vue +24 -0
  67. package/src/components/ui/EnableDisableToggle.vue +23 -0
  68. package/src/components/ui/FormField.vue +48 -48
  69. package/src/components/ui/IconLabel.vue +23 -0
  70. package/src/components/ui/InfoRow.vue +21 -54
  71. package/src/components/ui/Modal.vue +89 -56
  72. package/src/components/ui/Navbar.vue +60 -41
  73. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  74. package/src/components/ui/ReconnectIndicator.vue +111 -124
  75. package/src/components/ui/SectionCard.vue +6 -14
  76. package/src/components/ui/Splash.vue +2 -10
  77. package/src/components/ui/StatusBadge.vue +26 -28
  78. package/src/components/ui/TaskToggle.vue +54 -0
  79. package/src/components/ui/controls/CountryChooser.vue +27 -64
  80. package/src/components/ui/controls/EyeToggle.vue +1 -1
  81. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  82. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  83. package/src/components/ui/controls/atomic/MultiDropdown.vue +71 -119
  84. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  85. package/src/composables/useColorMapping.js +15 -0
  86. package/src/composables/useCopyToClipboard.js +1 -1
  87. package/src/composables/useDateFormatting.js +21 -0
  88. package/src/composables/useDeviceDetection.js +14 -0
  89. package/src/composables/useDropdownPosition.js +3 -4
  90. package/src/composables/useDynamicTableHeight.js +31 -0
  91. package/src/composables/useRowSelection.js +0 -3
  92. package/src/composables/useTicketPricing.js +16 -0
  93. package/src/composables/useWindowDimensions.js +21 -0
  94. package/src/libs/Filter.js +14 -20
  95. package/src/libs/panzoom.js +1 -5
  96. package/src/libs/utils/array.js +60 -0
  97. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  98. package/src/libs/utils/eventUrl.js +40 -0
  99. package/src/libs/utils/string.js +28 -0
  100. package/src/libs/utils/time.js +20 -0
  101. package/src/libs/utils/validation.js +88 -0
  102. package/src/main.js +0 -2
  103. package/src/stores/connection.js +1 -4
  104. package/src/stores/logger.js +6 -12
  105. package/src/stores/sampleData.js +1 -2
  106. package/src/stores/ui.js +59 -36
  107. package/src/views/Accounts.vue +13 -24
  108. package/src/views/Console.vue +70 -172
  109. package/src/views/Editor.vue +211 -379
  110. package/src/views/FilterBuilder.vue +188 -371
  111. package/src/views/Login.vue +3 -28
  112. package/src/views/Profiles.vue +8 -15
  113. package/src/views/Tasks.vue +49 -36
  114. package/tailwind.config.js +82 -71
  115. package/workbox-config.cjs +47 -5
  116. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  117. package/exit +0 -209
  118. package/run +0 -177
  119. package/src/assets/css/base/color-fallbacks.scss +0 -10
  120. package/switch-branch.sh +0 -41
  121. /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="input-container mb-3">
4
+ <div class="form-field-labeled mb-3">
5
5
  <div class="flex items-center gap-2">
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="input-container mb-4">
14
+ <div class="form-field-labeled mb-4">
22
15
  <div class="flex items-center gap-2">
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>
@@ -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>
@@ -51,14 +51,9 @@
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 { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
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";
@@ -71,7 +66,10 @@ const ui = useUIStore();
71
66
  const { copy } = useCopyToClipboard();
72
67
 
73
68
  const props = defineProps({
74
- account: { type: Object }
69
+ account: {
70
+ type: Object,
71
+ required: true
72
+ }
75
73
  });
76
74
 
77
75
  const enable = async () => await ui.addAccount({ ...props.account, enabled: true });
@@ -8,7 +8,7 @@
8
8
  <div>
9
9
  <div class="form-grid mb-4 mt-7">
10
10
  <div class="input-wrapper relative-positioned z-tooltip col-span-8">
11
- <label class="label-override mb-2">
11
+ <label class="label-override mb-2 flex">
12
12
  Account Tag
13
13
  <TagIcon />
14
14
  </label>
@@ -23,11 +23,11 @@
23
23
  </div>
24
24
 
25
25
  <div class="input-wrapper col-span-4">
26
- <label class="label-override mb-2">
26
+ <label class="label-override mb-2 flex">
27
27
  Threads
28
28
  <EditIcon />
29
29
  </label>
30
- <div :class="`input-default ${errors.includes('threads') ? 'error' : ''}`">
30
+ <div :class="`input-default ${errors.includes('threads') ? 'border-2 border-error-300' : ''}`">
31
31
  <input placeholder="1" type="number" min="1" max="50" v-model="creatorConfig.threads" />
32
32
  <div class="input-incrementer">
33
33
  <button @click="creatorConfig.threads++">
@@ -40,20 +40,20 @@
40
40
  </div>
41
41
  </div>
42
42
  <div class="input-wrapper col-span-8">
43
- <label class="label-override mb-2">
43
+ <label class="label-override mb-2 flex">
44
44
  Email catchall
45
45
  <MailIcon />
46
46
  </label>
47
- <div :class="`input-default ${errors.includes('catchall') ? 'error' : ''}`">
47
+ <div :class="`input-default ${errors.includes('catchall') ? 'border-2 border-error-300' : ''}`">
48
48
  <input placeholder="example.com" v-model="creatorConfig.catchall" />
49
49
  </div>
50
50
  </div>
51
51
  <div class="input-wrapper col-span-4">
52
- <label class="label-override mb-2">
52
+ <label class="label-override mb-2 flex">
53
53
  Catchall amount
54
54
  <BagIcon />
55
55
  </label>
56
- <div :class="`input-default ${errors.includes('number') ? 'error' : ''}`">
56
+ <div :class="`input-default ${errors.includes('number') ? 'border-2 border-error-300' : ''}`">
57
57
  <input placeholder="1" type="number" min="0" max="5000" v-model="creatorConfig.number" />
58
58
  <div class="input-incrementer">
59
59
  <button @click="creatorConfig.number++">
@@ -66,71 +66,28 @@
66
66
  </div>
67
67
  </div>
68
68
  <div class="input-wrapper col-span-12">
69
- <label class="label-override mb-2">
69
+ <label class="label-override mb-2 flex">
70
70
  Emails
71
71
  <MailIcon />
72
72
  </label>
73
- <div :class="`${errors.includes('emails') ? 'error-border' : ''}`">
74
- <textarea
75
- v-model="creatorConfig.emails"
76
- class="proxy-editor"
77
- spellcheck="false"
78
- style="max-height: 250px; min-height: 150px"
79
- placeholder="Enter emails here - One per line"></textarea>
80
- </div>
73
+ <textarea
74
+ v-model="creatorConfig.emails"
75
+ :class="['textarea-emails', errors.includes('emails') && 'textarea-error']"
76
+ spellcheck="false"
77
+ style="max-height: 250px; min-height: 150px; line-height: 1.6; tab-size: 4; font-family: 'JetBrains Mono', 'Fira Code', 'Menlo', 'Monaco', 'Courier New', monospace"
78
+ placeholder="Enter emails here - One per line"></textarea>
81
79
  </div>
82
80
  </div>
83
- <button
84
- class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
85
- @click="done()">
81
+ <button class="btn-modal ml-auto mt-4" @click="done()">
86
82
  Start
87
83
  <EditIcon />
88
84
  </button>
89
85
  </div>
90
86
  </Modal>
91
87
  </template>
92
- <style lang="scss" scoped>
93
- .input-wrapper {
94
- label {
95
- @apply flex;
96
- }
97
- }
98
- .error {
99
- border-width: 2px !important;
100
- border-color: rgb(238 130 130) !important;
101
- }
102
-
103
- .error-border {
104
- border: 2px solid rgb(238 130 130) !important;
105
- border-radius: 8px;
106
- }
107
-
108
- /* Proxy editor styles */
109
- .proxy-editor {
110
- width: 100%;
111
- background-color: oklch(0.19 0 0);
112
- color: oklch(0.90 0 0);
113
- font-family: "JetBrains Mono", "Fira Code", "Menlo", "Monaco", "Courier New", monospace;
114
- padding: 12px;
115
- border: none;
116
- resize: none;
117
- font-size: 14px;
118
- line-height: 1.6;
119
- tab-size: 4;
120
- outline: none;
121
- border: 1px solid oklch(0.26 0 0);
122
- border-radius: 8px;
123
- overflow: auto;
124
- }
125
-
126
- .proxy-editor:focus {
127
- border-color: oklch(0.28 0 0);
128
- box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.2);
129
- }
130
- </style>
131
88
  <script setup>
132
89
  import Modal from "@/components/ui/Modal.vue";
133
- import { EditIcon, TagIcon, SpinnerIcon, UpIcon, DownIcon, MailIcon, BagIcon } from "@/components/icons";
90
+ import { EditIcon, TagIcon, UpIcon, DownIcon, MailIcon, BagIcon } from "@/components/icons";
134
91
  import { useUIStore } from "@/stores/ui";
135
92
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
136
93
 
@@ -168,3 +125,15 @@ function done() {
168
125
  ui.createAcconts(creatorConfig.value);
169
126
  }
170
127
  </script>
128
+ <style scoped>
129
+ .textarea-emails {
130
+ @apply w-full rounded border bg-dark-350 p-3 font-mono text-light-300;
131
+ @apply outline-none resize-none overflow-auto text-sm;
132
+ @apply border border-dark-550 hover:border-dark-650;
133
+ @apply focus:border-dark-700 focus:shadow-focus-ring;
134
+ }
135
+
136
+ .textarea-error {
137
+ @apply border-2 border-error-300 hover:border-error-300;
138
+ }
139
+ </style>
@@ -1,94 +1,71 @@
1
1
  <template>
2
- <Table>
3
- <Header class="sticky top-0 z-10 grid-cols-5 bg-dark-400 text-center md:grid-cols-7">
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-3"
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')">
10
+ <div class="mx-auto flex cursor-pointer items-center" @click="ui.toggleSort('eventId')">
11
11
  <MailIcon class="mr-0 h-4 w-4 md:mr-3" />
12
- <h4 class="hidden md:flex">Email</h4>
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
16
  <KeyIcon class="mr-0 h-4 w-4 md:mr-3" />
17
- <h4 class="hidden md:flex">Password</h4>
17
+ <h4 class="hidden text-white md:flex">Password</h4>
18
18
  </div>
19
- <div class="col-span-1 flex items-center justify-center" v-once>
19
+ <div class="grid-cell-center" v-once>
20
20
  <CheckmarkIcon class="mr-0 h-4 w-4 md:mr-3" />
21
- <h4 class="hidden md:flex">Enabled</h4>
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
24
  <TicketIcon class="mr-0 h-4 w-4 md:mr-3" />
25
- <h4 class="hidden md:flex">Tags</h4>
25
+ <h4 class="hidden text-white md:flex">Tags</h4>
26
26
  </div>
27
- <div class="col-span-1 flex items-center justify-center" v-once>
27
+ <div class="grid-cell-center" v-once>
28
28
  <ClickIcon class="mr-0 h-4 w-4 md:mr-3" />
29
- <h4 class="hidden md:flex">Actions</h4>
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 stop-pan flex flex-col divide-y divide-dark-650 overflow-y-auto overflow-x-hidden"
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 v-for="(account, i) in toRender" :key="account.id || account.index" class="account-row-container">
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
41
  :class="i % 2 == 1 ? 'table-row-even' : 'table-row-odd'"
39
42
  :account="account" />
40
43
  </div>
41
44
  </div>
42
- <div v-else class="empty-state flex flex-col items-center justify-center bg-dark-400 py-8 text-center">
43
- <MailIcon class="mb-3 h-12 w-12 text-dark-400 opacity-50" />
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 { Table, Header } from "@/components/Table";
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 { computed, ref, onMounted, onUnmounted } from "vue";
61
+ import { useDynamicTableHeight } from "@/composables/useDynamicTableHeight";
62
+ import { computed, ref } from "vue";
89
63
 
90
64
  const props = defineProps({
91
- accounts: { type: Object }
65
+ accounts: {
66
+ type: Object,
67
+ required: true
68
+ }
92
69
  });
93
70
  const ui = useUIStore();
94
71
 
@@ -110,43 +87,18 @@ const toRender = computed(() => {
110
87
  return rendered;
111
88
  });
112
89
 
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;
90
+ // Layout constants for dynamic table height calculation
91
+ const LAYOUT_CONSTANTS = {
92
+ TOP_RESERVED_SPACE: 180, // Page header + search controls + gaps (router-wrapper handles navbar, no UTILS section)
93
+ BOTTOM_BUFFER: 50, // Margin at bottom to prevent overflow
94
+ ROW_HEIGHT: 64, // Account row height in pixels
95
+ MIN_ROWS_TO_SHOW: 2 // Minimum number of rows to display
120
96
  };
121
97
 
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;
140
-
141
- // Account row height is always 64px
142
- const rowHeight = 64;
143
- const minRowsToShow = 2;
144
- const minHeight = minRowsToShow * rowHeight;
145
-
146
- // Calculate exact number of complete rows that fit with conservative approach
147
- const maxCompleteRows = Math.floor(Math.max(availableHeight, minHeight) / rowHeight);
148
- const exactHeight = maxCompleteRows * rowHeight;
149
-
150
- return exactHeight + "px";
98
+ const { dynamicTableHeight } = useDynamicTableHeight({
99
+ topReservedSpace: LAYOUT_CONSTANTS.TOP_RESERVED_SPACE,
100
+ bottomBuffer: LAYOUT_CONSTANTS.BOTTOM_BUFFER,
101
+ rowHeight: LAYOUT_CONSTANTS.ROW_HEIGHT,
102
+ minRowsToShow: LAYOUT_CONSTANTS.MIN_ROWS_TO_SHOW
151
103
  });
152
104
  </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
- <div class="input-wrapper col-span-4">
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
- </div>
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
- <div v-if="ui.currentlyEditing?.email" class="mt-6 grid grid-cols-12 gap-3 pt-4 border-t border-dark-600">
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,17 +66,11 @@ import {
102
66
  EditIcon,
103
67
  MailIcon,
104
68
  KeyIcon,
105
- ProfileIcon,
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 StatusBadge from "@/components/ui/StatusBadge.vue";
114
- import TagLabel from "@/components/Editors/TagLabel.vue";
115
-
73
+ import ReadonlyFieldsSection from "@/components/ui/ReadonlyFieldsSection.vue";
116
74
  import { ref } from "vue";
117
75
 
118
76
  const ui = useUIStore();