@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.
Files changed (137) 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 +70 -566
  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 +61 -74
  13. package/src/assets/css/components/forms.scss +31 -32
  14. package/src/assets/css/components/headers.scss +13 -21
  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 +295 -0
  20. package/src/assets/css/main.scss +55 -139
  21. package/src/components/Auth/LoginForm.vue +7 -86
  22. package/src/components/Console/ConsoleToolbar.vue +123 -0
  23. package/src/components/Editors/Account/Account.vue +12 -12
  24. package/src/components/Editors/Account/AccountView.vue +38 -111
  25. package/src/components/Editors/Account/CreateAccount.vue +11 -61
  26. package/src/components/Editors/Account/{AccountCreator.vue → CreateAccountBatch.vue} +28 -59
  27. package/src/components/Editors/AdminFileEditor.vue +179 -0
  28. package/src/components/Editors/Profile/CreateProfile.vue +77 -150
  29. package/src/components/Editors/Profile/Profile.vue +20 -21
  30. package/src/components/Editors/Profile/ProfileCountryChooser.vue +16 -60
  31. package/src/components/Editors/Profile/ProfileView.vue +41 -116
  32. package/src/components/Editors/ProxyFileEditor.vue +86 -0
  33. package/src/components/Editors/TagLabel.vue +16 -55
  34. package/src/components/Editors/TagToggle.vue +20 -8
  35. package/src/components/Filter/Filter.vue +66 -79
  36. package/src/components/Filter/FilterPreview.vue +153 -135
  37. package/src/components/Filter/PriceSortToggle.vue +36 -43
  38. package/src/components/Table/Header.vue +1 -1
  39. package/src/components/Table/Table.vue +45 -51
  40. package/src/components/Tasks/CheckStock.vue +7 -16
  41. package/src/components/Tasks/Controls/DesktopControls.vue +15 -60
  42. package/src/components/Tasks/Controls/MobileControls.vue +5 -20
  43. package/src/components/Tasks/CreateTaskAXS.vue +20 -118
  44. package/src/components/Tasks/CreateTaskTM.vue +33 -189
  45. package/src/components/Tasks/EventDetailRow.vue +21 -0
  46. package/src/components/Tasks/MassEdit.vue +6 -16
  47. package/src/components/Tasks/QuickSettings.vue +140 -216
  48. package/src/components/Tasks/ScrapeVenue.vue +4 -13
  49. package/src/components/Tasks/Stats.vue +20 -39
  50. package/src/components/Tasks/Task.vue +64 -270
  51. package/src/components/Tasks/TaskLabel.vue +9 -3
  52. package/src/components/Tasks/TaskView.vue +45 -64
  53. package/src/components/Tasks/Utilities.vue +10 -44
  54. package/src/components/Tasks/ViewTask.vue +23 -107
  55. package/src/components/icons/Close.vue +2 -8
  56. package/src/components/icons/Gear.vue +8 -8
  57. package/src/components/icons/Hash.vue +5 -0
  58. package/src/components/icons/Key.vue +2 -8
  59. package/src/components/icons/Pencil.vue +2 -8
  60. package/src/components/icons/Profile.vue +2 -8
  61. package/src/components/icons/Sell.vue +2 -8
  62. package/src/components/icons/Spinner.vue +4 -7
  63. package/src/components/icons/Wildcard.vue +2 -8
  64. package/src/components/icons/index.js +3 -5
  65. package/src/components/ui/ActionButtonGroup.vue +113 -52
  66. package/src/components/ui/BalanceIndicator.vue +60 -0
  67. package/src/components/ui/EmptyState.vue +24 -0
  68. package/src/components/ui/EnableDisableToggle.vue +23 -0
  69. package/src/components/ui/FormField.vue +49 -49
  70. package/src/components/ui/IconLabel.vue +23 -0
  71. package/src/components/ui/InfoRow.vue +21 -54
  72. package/src/components/ui/Modal.vue +161 -54
  73. package/src/components/ui/Navbar.vue +63 -44
  74. package/src/components/ui/ReadonlyFieldsSection.vue +31 -0
  75. package/src/components/ui/ReconnectIndicator.vue +111 -124
  76. package/src/components/ui/SectionCard.vue +6 -14
  77. package/src/components/ui/Splash.vue +2 -10
  78. package/src/components/ui/StatusBadge.vue +26 -28
  79. package/src/components/ui/TaskToggle.vue +54 -0
  80. package/src/components/ui/controls/CountryChooser.vue +29 -66
  81. package/src/components/ui/controls/EyeToggle.vue +1 -1
  82. package/src/components/ui/controls/atomic/Checkbox.vue +40 -121
  83. package/src/components/ui/controls/atomic/Dropdown.vue +103 -139
  84. package/src/components/ui/controls/atomic/MultiDropdown.vue +72 -120
  85. package/src/components/ui/controls/atomic/Switch.vue +21 -84
  86. package/src/composables/useCodeEditor.js +117 -0
  87. package/src/composables/useColorMapping.js +15 -0
  88. package/src/composables/useCopyToClipboard.js +1 -1
  89. package/src/composables/useDateFormatting.js +21 -0
  90. package/src/composables/useDeviceDetection.js +14 -0
  91. package/src/composables/useDropdownPosition.js +1 -4
  92. package/src/composables/useDynamicTableHeight.js +31 -0
  93. package/src/composables/useEnableDisable.js +6 -0
  94. package/src/composables/useFilterCSS.js +71 -0
  95. package/src/composables/useFormValidation.js +92 -0
  96. package/src/composables/useGetAllTags.js +9 -0
  97. package/src/composables/useIOSViewportHandling.js +76 -0
  98. package/src/composables/useNotchHandling.js +306 -0
  99. package/src/composables/useRowSelection.js +0 -3
  100. package/src/composables/useTableRender.js +23 -0
  101. package/src/composables/useTicketPricing.js +16 -0
  102. package/src/composables/useWindowDimensions.js +21 -0
  103. package/src/composables/useZoomPrevention.js +96 -0
  104. package/src/constants/tableLayout.js +14 -0
  105. package/src/libs/Filter.js +14 -20
  106. package/src/libs/panzoom.js +1 -5
  107. package/src/libs/utils/array.js +58 -0
  108. package/src/{stores/utils.js → libs/utils/dataGeneration.js} +2 -250
  109. package/src/libs/utils/eventUrl.js +40 -0
  110. package/src/libs/utils/string.js +3 -0
  111. package/src/libs/utils/time.js +20 -0
  112. package/src/libs/utils/validation.js +64 -0
  113. package/src/main.js +0 -2
  114. package/src/stores/connection.js +1 -29
  115. package/src/stores/logger.js +6 -12
  116. package/src/stores/sampleData.js +1 -2
  117. package/src/stores/ui.js +80 -71
  118. package/src/utils/tableHelpers.js +1 -0
  119. package/src/views/Accounts.vue +19 -38
  120. package/src/views/Console.vue +74 -253
  121. package/src/views/Editor.vue +47 -1114
  122. package/src/views/FilterBuilder.vue +190 -461
  123. package/src/views/Login.vue +3 -28
  124. package/src/views/Profiles.vue +17 -32
  125. package/src/views/Tasks.vue +51 -38
  126. package/tailwind.config.js +82 -71
  127. package/workbox-config.cjs +47 -5
  128. package/docs/plans/2026-02-08-tailwind-consolidation.md +0 -2438
  129. package/exit +0 -209
  130. package/run +0 -177
  131. package/src/assets/css/base/color-fallbacks.scss +0 -10
  132. package/src/assets/img/background.svg.backup +0 -11
  133. package/src/components/icons/SquareCheck.vue +0 -18
  134. package/src/components/icons/SquareUncheck.vue +0 -18
  135. package/src/components/ui/controls/atomic/LoadingButton.vue +0 -45
  136. package/switch-branch.sh +0 -41
  137. /package/public/{reconnect-logo.png → img/reconnect-logo.png} +0 -0
@@ -9,21 +9,17 @@
9
9
  <div>
10
10
  <div class="my-3 grid grid-cols-12 gap-3">
11
11
  <!-- Profile tag -->
12
- <div class="input-wrapper col-span-4">
13
- <label class="label-override mb-2">
14
- Profile Tag
15
- <TagIcon />
16
- </label>
12
+ <FormField label="Profile 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`"
19
15
  :default="ui.profile.tags[0]"
20
16
  :options="ui.profile.tags"
21
- :onClick="(f) => (profile.tag = f)"
17
+ :onClick="(f) => (formProfile.tag = f)"
22
18
  :capitalize="true" />
23
- </div>
19
+ </FormField>
24
20
 
25
21
  <!-- Card Number -->
26
- <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-8">
22
+ <FormField label="Card Number" :icon="CartIcon" :error="errors.includes('cardNumber')" z-index="0" class="col-span-12 md:col-span-8">
27
23
  <input
28
24
  ref="cardNumberInput"
29
25
  placeholder="Enter card number"
@@ -35,51 +31,39 @@
35
31
  </FormField>
36
32
 
37
33
  <!-- Country chooser -->
38
- <div class="input-wrapper col-span-2">
39
- <label class="label-override mb-2">
40
- Country
41
- <StadiumIcon />
42
- </label>
34
+ <FormField label="Country" :icon="StadiumIcon" z-index="0" class="col-span-12 md:col-span-2" noWrapper>
43
35
  <ProfileCountryChooser
44
- class="h-8"
45
- :value="profile.country"
36
+ class="h-10"
37
+ :value="formProfile.country"
46
38
  :onClick="chooseCountry"
47
39
  :disabled="true" />
48
- </div>
40
+ </FormField>
49
41
 
50
42
  <!-- Exp Year -->
51
- <div class="input-wrapper col-span-5">
52
- <label class="label-override mb-2">
53
- Expiry Year
54
- <TimerIcon />
55
- </label>
43
+ <FormField label="Expiry Year" :icon="TimerIcon" :error="errors.includes('expYear')" z-index="0" class="col-span-6 md:col-span-5" noWrapper>
56
44
  <Dropdown
57
45
  :class="`input-default dropdown w-full ${errors.includes('expYear') ? 'error' : ''}`"
58
46
  default="Expiry Year"
59
47
  :value="
60
- profile.expYear && !profile?.expYear?.startsWith('20')
61
- ? '20' + profile.expYear
62
- : profile.expYear
63
- ? profile.expYear
48
+ formProfile.expYear && !formProfile?.expYear?.startsWith('20')
49
+ ? '20' + formProfile.expYear
50
+ : formProfile.expYear
51
+ ? formProfile.expYear
64
52
  : undefined
65
53
  "
66
54
  :options="['2025', '2026', '2027', '2028', '2029', '2030', '2031']"
67
- :onClick="(f) => (profile.expYear = f)" />
68
- </div>
55
+ :onClick="(f) => (formProfile.expYear = f)" />
56
+ </FormField>
69
57
 
70
58
  <!-- Exp Month -->
71
- <div class="input-wrapper col-span-5">
72
- <label class="label-override mb-2">
73
- Expiry Month
74
- <TimerIcon />
75
- </label>
59
+ <FormField label="Expiry Month" :icon="TimerIcon" :error="errors.includes('expMonth')" z-index="0" class="col-span-6 md:col-span-5" noWrapper>
76
60
  <Dropdown
77
61
  :class="`input-default dropdown w-full ${errors.includes('expMonth') ? 'error' : ''}`"
78
62
  default="Expiry Month"
79
- :value="profile.expMonth ? profile.expMonth : undefined"
63
+ :value="formProfile.expMonth ? formProfile.expMonth : undefined"
80
64
  :options="['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']"
81
- :onClick="(f) => (profile.expMonth = f)" />
82
- </div>
65
+ :onClick="(f) => (formProfile.expMonth = f)" />
66
+ </FormField>
83
67
 
84
68
  <!-- CVV -->
85
69
  <FormField label="CVV" :icon="ShieldIcon" :error="errors.includes('cvv')" z-index="0" class="col-span-6 md:col-span-4">
@@ -89,55 +73,45 @@
89
73
  max="9999"
90
74
  maxlength="4"
91
75
  minlength="3"
92
- v-model="profile.cvv" />
76
+ v-model="formProfile.cvv" />
93
77
  </FormField>
94
78
 
95
79
  <!-- City -->
96
80
  <FormField label="City" :icon="StadiumIcon" :error="errors.includes('city')" z-index="0" class="col-span-6 md:col-span-4">
97
- <input placeholder="Denver" v-model="profile.city" />
81
+ <input placeholder="Denver" v-model="formProfile.city" />
98
82
  </FormField>
99
83
 
100
84
  <!-- State -->
101
- <div class="input-wrapper col-span-6 md:col-span-4">
102
- <label class="label-override mb-2">
103
- State
104
- <SandclockIcon />
105
- </label>
106
- <div v-if="profile.country === 'US'">
107
- <Dropdown
108
- class="input-default w-full"
109
- default="Select State"
110
- :onClick="(state) => (profile.state = state)"
111
- :options="usStates"
112
- :allowDefault="false"
113
- rightAmount="right-2"
114
- :value="profile.state" />
115
- </div>
85
+ <FormField label="State" :icon="SandclockIcon" z-index="0" class="col-span-6 md:col-span-4" noWrapper>
86
+ <Dropdown
87
+ v-if="formProfile.country === 'US'"
88
+ class="input-default w-full"
89
+ default="Select State"
90
+ :onClick="(state) => (formProfile.state = state)"
91
+ :options="usStates"
92
+ :allowDefault="false"
93
+ rightAmount="right-2"
94
+ :value="formProfile.state" />
116
95
  <div v-else class="input-default">
117
96
  <input disabled placeholder="N/A" value="" />
118
97
  </div>
119
- </div>
98
+ </FormField>
120
99
 
121
100
  <!-- Zip -->
122
101
  <FormField label="Zip" :icon="KeyIcon" :error="errors.includes('zipCode')" z-index="0" class="col-span-6 md:col-span-4">
123
- <input placeholder="10005" type="number" min="1" max="12" v-model="profile.zipCode" />
102
+ <input placeholder="10005" type="number" min="1" max="12" v-model="formProfile.zipCode" />
124
103
  </FormField>
125
104
 
126
105
  <!-- Address -->
127
106
  <FormField label="Address" :icon="HandIcon" :error="errors.includes('address')" z-index="0" class="col-span-6 md:col-span-4">
128
- <input placeholder="100 5th Avenue" v-model="profile.address" />
107
+ <input placeholder="100 5th Avenue" v-model="formProfile.address" />
129
108
  </FormField>
130
109
 
131
110
  <!-- Generate -->
132
- <div class="input-wrapper z-0 col-span-6 md:col-span-4">
133
- <label class="label-override mb-2">
134
- Fake ID
135
- <WildcardIcon />
136
- </label>
137
- <div class="input-default mt-2 flex h-10 w-full items-center">
138
- <button
139
- @click="generate"
140
- class="flex w-full items-center justify-center gap-2 text-xs text-white">
111
+ <FormField label="Fake ID" :icon="WildcardIcon" z-index="0" class="col-span-6 md:col-span-4">
112
+ <button
113
+ @click="generate"
114
+ class="flex h-full w-full items-center justify-center gap-2 text-xs text-white">
141
115
  <svg
142
116
  xmlns="http://www.w3.org/2000/svg"
143
117
  width="14"
@@ -153,81 +127,52 @@
153
127
  <line x1="17" y1="2" x2="17" y2="6" />
154
128
  </svg>
155
129
  <span>Generate</span>
156
- </button>
157
- </div>
158
- </div>
130
+ </button>
131
+ </FormField>
159
132
  </div>
160
133
 
161
134
  <!-- Readonly fields when editing -->
162
- <div v-if="ui.currentlyEditing?.profileName" class="mt-6 grid grid-cols-12 gap-3 pt-4 border-t border-dark-600">
163
- <div v-if="ui.currentlyEditing.tags && ui.currentlyEditing.tags.length > 0" class="col-span-6">
164
- <label class="label-override mb-2">Tags</label>
165
- <div class="flex gap-2 flex-wrap">
166
- <TagLabel v-for="tag in ui.currentlyEditing.tags" :key="tag" :text="tag" />
167
- </div>
168
- </div>
169
- <div class="col-span-6">
170
- <label class="label-override mb-2">Status</label>
171
- <div class="flex items-center gap-3 h-10">
172
- <StatusBadge :enabled="ui.currentlyEditing.enabled" size="large" />
173
- <span class="text-sm font-medium" :class="ui.currentlyEditing.enabled ? 'text-green-400' : 'text-red-400'">
174
- {{ ui.currentlyEditing.enabled ? 'Enabled' : 'Disabled' }}
175
- </span>
176
- </div>
177
- </div>
178
- </div>
135
+ <ReadonlyFieldsSection v-if="ui.currentlyEditing?.profileName" :data="ui.currentlyEditing" />
179
136
  </div>
180
137
 
181
- <button
182
- class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
183
- @click="done()">
138
+ <button class="btn-modal ml-auto mt-4 w-48" @click="done()">
184
139
  Save
185
- <EditIcon />
140
+ <EditIcon class="ml-2" />
186
141
  </button>
187
142
  </Modal>
188
143
  </template>
189
- <style lang="scss" scoped>
190
- .label-override {
191
- @apply flex items-center;
192
- color: #e1e1e4 !important;
193
-
194
- svg {
195
- @apply ml-2;
196
-
197
- path {
198
- fill: #e1e1e4 !important;
199
- }
200
- }
201
- }
202
- </style>
203
144
  <script setup>
204
145
  import Modal from "@/components/ui/Modal.vue";
205
146
  import FormField from "@/components/ui/FormField.vue";
206
147
  import {
207
- MailIcon,
208
148
  CartIcon,
209
149
  ShieldIcon,
210
150
  StadiumIcon,
211
151
  KeyIcon,
212
152
  HandIcon,
213
- ProfileIcon,
214
153
  SandclockIcon,
215
154
  TimerIcon,
216
155
  TagIcon,
217
156
  WildcardIcon
218
157
  } from "@/components/icons";
219
158
  import { EditIcon } from "@/components/icons";
220
- import StatusBadge from "@/components/ui/StatusBadge.vue";
221
- import TagLabel from "@/components/Editors/TagLabel.vue";
159
+ import ReadonlyFieldsSection from "@/components/ui/ReadonlyFieldsSection.vue";
222
160
  import ProfileCountryChooser from "@/components/Editors/Profile/ProfileCountryChooser.vue";
223
161
  import { useUIStore } from "@/stores/ui";
224
162
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
225
- import { ref, computed, watch, nextTick, onMounted } from "vue";
226
- import { fakeId } from "@/stores/utils";
227
- import { validateCard } from "@/stores/utils";
163
+ import { ref, watch, nextTick, onMounted } from "vue";
164
+ import { fakeId } from "@/libs/utils/dataGeneration";
165
+ import { validateCard } from "@/libs/utils/validation";
166
+ import { useFormValidation } from "@/composables/useFormValidation";
167
+
168
+ defineProps({
169
+ profile: {
170
+ type: Object,
171
+ default: null
172
+ }
173
+ });
228
174
 
229
- const props = defineProps({ profile: { type: Object, required: false } });
230
- const errors = ref([]);
175
+ const { errors, validateProfile } = useFormValidation();
231
176
  const ui = useUIStore();
232
177
 
233
178
  // US States list (excluding GA and PR, alphabetically ordered)
@@ -283,7 +228,7 @@ const usStates = [
283
228
  "WY"
284
229
  ];
285
230
  const cardNumberInput = ref(null);
286
- const profile = ref({
231
+ const formProfile = ref({
287
232
  cvv: "",
288
233
  cardNumber: "",
289
234
  city: "",
@@ -293,15 +238,15 @@ const profile = ref({
293
238
  zipCode: ""
294
239
  });
295
240
 
296
- if (ui.currentlyEditing?.profileName) profile.value = ui.currentlyEditing;
241
+ if (ui.currentlyEditing?.profileName) formProfile.value = ui.currentlyEditing;
297
242
 
298
243
  // Reactive display value for the formatted card number
299
244
  const displayCardNumber = ref("");
300
245
 
301
246
  // Initialize display card number with formatting
302
247
  const initializeCardNumberDisplay = () => {
303
- if (profile.value.cardNumber) {
304
- const cleanNumber = profile.value.cardNumber.replace(/\s+/g, "");
248
+ if (formProfile.value.cardNumber) {
249
+ const cleanNumber = formProfile.value.cardNumber.replace(/\s+/g, "");
305
250
  if (cleanNumber) {
306
251
  const cardInfo = validateCard(cleanNumber);
307
252
  displayCardNumber.value = cardInfo.formatted;
@@ -311,8 +256,8 @@ const initializeCardNumberDisplay = () => {
311
256
 
312
257
  // Format card number display on focus
313
258
  const formatCardNumberDisplay = () => {
314
- if (profile.value.cardNumber) {
315
- const cleanNumber = profile.value.cardNumber.replace(/\s+/g, "");
259
+ if (formProfile.value.cardNumber) {
260
+ const cleanNumber = formProfile.value.cardNumber.replace(/\s+/g, "");
316
261
  if (cleanNumber) {
317
262
  const cardInfo = validateCard(cleanNumber);
318
263
  displayCardNumber.value = cardInfo.formatted;
@@ -322,7 +267,7 @@ const formatCardNumberDisplay = () => {
322
267
 
323
268
  // Watch for changes in profile.cardNumber to sync with display
324
269
  watch(
325
- () => profile.value.cardNumber,
270
+ () => formProfile.value.cardNumber,
326
271
  (newValue) => {
327
272
  if (newValue) {
328
273
  const cleanNumber = newValue.replace(/\s+/g, "");
@@ -345,39 +290,21 @@ onMounted(() => {
345
290
 
346
291
  const generate = () => {
347
292
  const fake = fakeId();
348
- profile.value.city = fake.city;
349
- profile.value.zipCode = fake?.zipCode;
350
- profile.value.address = fake.address;
351
- profile.value.state = fake.state;
352
- profile.value.country = "US";
293
+ formProfile.value.city = fake.city;
294
+ formProfile.value.zipCode = fake?.zipCode;
295
+ formProfile.value.address = fake.address;
296
+ formProfile.value.state = fake.state;
297
+ formProfile.value.country = "US";
353
298
  };
354
299
 
355
300
  const chooseCountry = (f) => {
356
- profile.value.country = f;
357
- if (f !== "US") profile.value.state = "";
301
+ formProfile.value.country = f;
302
+ if (f !== "US") formProfile.value.state = "";
358
303
  };
359
304
 
360
305
  const validate = (p) => {
361
- errors.value = [];
362
306
  if (!p.zipCode && !p.address && !p.state && !p.city) generate();
363
-
364
- if (!p.zipCode) errors.value.push("zipCode");
365
- if (!p.address) errors.value.push("address");
366
- if (!p.state && p.country === "US") errors.value.push("state");
367
- if (!p.city) errors.value.push("city");
368
- if (!p.country) errors.value.push("country");
369
- if (!/^\d{3,4}$/.test(`${p.cvv}`)) errors.value.push("cvv");
370
- const cleanCardNumber = p.cardNumber.replace(/\s+/g, "");
371
- // Validate card number based on type and length
372
- const isValidCard =
373
- cleanCardNumber.match(/^4\d{15}$/) || // Visa (16 digits)
374
- cleanCardNumber.match(/^5[1-5]\d{14}$/) || // Mastercard (16 digits)
375
- cleanCardNumber.match(/^3[47]\d{13}$/); // AMEX (15 digits)
376
- if (!isValidCard) errors.value.push("cardNumber");
377
- if (!p.expYear) errors.value.push("expYear");
378
- if (!p.expMonth) errors.value.push("expMonth");
379
- if (!p.expMonth) errors.value.push("expMonth");
380
- return errors.value.length === 0;
307
+ return validateProfile(p);
381
308
  };
382
309
 
383
310
  const handleCreditCardUpdate = (event) => {
@@ -395,20 +322,20 @@ const handleCreditCardUpdate = (event) => {
395
322
  const cardInfo = validateCard(subs);
396
323
 
397
324
  // Store clean number (without spaces) in profile
398
- profile.value.cardNumber = subs;
325
+ formProfile.value.cardNumber = subs;
399
326
  // Display formatted number in input
400
327
  displayCardNumber.value = cardInfo.formatted;
401
328
  };
402
329
 
403
330
  function done() {
404
331
  // Clear state if country is not US
405
- if (profile.value.country !== "US") {
406
- profile.value.state = "";
332
+ if (formProfile.value.country !== "US") {
333
+ formProfile.value.state = "";
407
334
  }
408
335
 
409
- ui.logger.Info("Created profile", profile.value);
410
- if (validate(profile.value) !== true) return;
336
+ ui.logger.Info("Created profile", formProfile.value);
337
+ if (validate(formProfile.value) !== true) return;
411
338
  ui.toggleModal("");
412
- ui.addProfile(profile.value);
339
+ ui.addProfile(formProfile.value);
413
340
  }
414
341
  </script>
@@ -1,22 +1,22 @@
1
1
  <template>
2
2
  <Row
3
- class="relative grid-cols-7 text-white lg:grid-cols-8"
3
+ class="relative h-16 grid-cols-4 sm:grid-cols-5 md:grid-cols-7 lg:grid-cols-8 text-white"
4
4
  @click="ui.setOpenContextMenu('')"
5
5
  @click.right.prevent="ui.setOpenContextMenu('')"
6
6
  @dblclick="handleDoubleClick"
7
7
  @touchstart="handleTouchStart"
8
8
  @touchend="handleTouchEnd">
9
- <div class="col-span-3 flex lg:col-span-2">
9
+ <div class="col-span-2 sm:col-span-2 md:col-span-3 lg:col-span-2 flex">
10
10
  <Checkbox
11
11
  class="ml-0 mr-4"
12
12
  :toggled="props.profile.selected"
13
13
  @valueUpdate="ui.toggleProfileSelected(props.profile.id)" />
14
- <h4 class="mx-auto text-white">
14
+ <h4 class="mx-auto text-center text-white">
15
15
  {{ props.profile.profileName }}
16
16
  </h4>
17
17
  </div>
18
- <div class="col-span-1 lg:col-span-2">
19
- <h4 class="flex items-center justify-center gap-2 text-white">
18
+ <div class="col-span-1 hidden md:block lg:col-span-2">
19
+ <h4 class="flex items-center justify-center gap-2 text-center text-white">
20
20
  <span class="hidden sm:block">
21
21
  {{
22
22
  props.profile.privacy
@@ -29,20 +29,20 @@
29
29
  <img class="h-6 w-6" :src="getAccountType()" />
30
30
  </h4>
31
31
  </div>
32
- <div class="col-span-1">
33
- <h4 class="text-white">{{ expDate() }}</h4>
32
+ <div class="col-span-1 hidden md:block">
33
+ <h4 class="text-center text-white">{{ expDate() }}</h4>
34
34
  </div>
35
35
  <div class="col-span-1 flex justify-center">
36
36
  <StatusBadge :enabled="props.profile.enabled" size="small" />
37
37
  </div>
38
38
 
39
39
  <div class="col-span-1 hidden lg:block">
40
- <h4 class="flex justify-center gap-1 text-white">
40
+ <h4 class="flex justify-center gap-1 text-center text-white">
41
41
  <TagLabel v-for="tag in props.profile.tags" :key="tag" :text="tag" />
42
42
  </h4>
43
43
  </div>
44
44
 
45
- <div class="col-span-1 flex">
45
+ <div class="col-span-1 sm:col-span-1 flex">
46
46
  <ActionButtonGroup>
47
47
  <li>
48
48
  <button @click="edit">
@@ -51,12 +51,12 @@
51
51
  </li>
52
52
  <li v-if="props.profile.enabled">
53
53
  <button @click="disable">
54
- <img class="h-4 w-4" src="/img/controls/disable.svg" />
54
+ <img class="icon-md" src="/img/controls/disable.svg" />
55
55
  </button>
56
56
  </li>
57
57
  <li v-else>
58
58
  <button @click="enable">
59
- <img class="h-4 w-4" src="/img/controls/enable.svg" />
59
+ <img class="icon-md" src="/img/controls/enable.svg" />
60
60
  </button>
61
61
  </li>
62
62
  <li>
@@ -66,26 +66,26 @@
66
66
  </div>
67
67
  </Row>
68
68
  </template>
69
- <style lang="scss" scoped>
70
- h4 {
71
- @apply text-center;
72
- }
73
- </style>
74
69
  <script setup>
75
70
  import { Row } from "@/components/Table";
76
- import { PlayIcon, TrashIcon, BagWhiteIcon, PauseIcon, EditIcon } from "@/components/icons";
71
+ import { TrashIcon, EditIcon } from "@/components/icons";
77
72
  import Checkbox from "@/components/ui/controls/atomic/Checkbox.vue";
78
73
  import StatusBadge from "@/components/ui/StatusBadge.vue";
79
74
  import ActionButtonGroup from "@/components/ui/ActionButtonGroup.vue";
80
75
  import { useUIStore } from "@/stores/ui";
81
- import { validateCard } from "@/stores/utils";
76
+ import { validateCard } from "@/libs/utils/validation";
82
77
  import TagLabel from "@/components/Editors/TagLabel.vue";
83
78
  import { useRowSelection } from "@/composables/useRowSelection";
79
+ import { computed } from "vue";
80
+ import { useEnableDisable } from "@/composables/useEnableDisable";
84
81
 
85
82
  const ui = useUIStore();
86
83
 
87
84
  const props = defineProps({
88
- profile: { type: Object }
85
+ profile: {
86
+ type: Object,
87
+ required: true
88
+ }
89
89
  });
90
90
 
91
91
  const getAccountType = () => {
@@ -99,8 +99,7 @@ const getAccountType = () => {
99
99
 
100
100
  const expDate = () =>
101
101
  props.profile.privacy ? "••/••" : `${props.profile.expMonth}/${props.profile.expYear?.replace("20", "")}`;
102
- const enable = async () => await ui.addProfile({ ...props.profile, enabled: true });
103
- const disable = async () => await ui.addProfile({ ...props.profile, enabled: false });
102
+ const { enable, disable } = useEnableDisable(computed(() => props.profile), ui.addProfile);
104
103
  const edit = () => {
105
104
  ui.currentlyEditing = props.profile;
106
105
  ui.toggleModal("create-profile");
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown rounded-lg" ref="dropdownRef">
3
+ <div class="dropdown input-default h-10 w-12 md:h-10 flex justify-center items-center p-0 bg-dark-550 rounded-lg [background-clip:border-box]" ref="dropdownRef">
4
4
  <span @click="toggleOpen" class="flex justify-between items-center z-50 text-white">
5
5
  <div class="flex gap-3 justify-center">
6
6
  <img class="w-5" :src="`/flags/${current?.toLowerCase()}.svg`" />
@@ -9,7 +9,7 @@
9
9
  <Teleport to="body">
10
10
  <div
11
11
  v-if="open && !disabled"
12
- class="dropdown-content-portal special-dropdown"
12
+ class="min-w-20 bg-dark-400 border border-dark-650 rounded-lg shadow-2xl z-50 p-2 [max-height:192px] overflow-y-auto custom-scrollbar-y"
13
13
  :style="menuStyle"
14
14
  @click.stop
15
15
  @wheel.stop
@@ -17,9 +17,9 @@
17
17
  <div
18
18
  v-for="(country, i) in countries"
19
19
  v-bind:key="country"
20
- :class="`cursor-pointer w-12 ${i === 0 ? '' : 'my-2'}`"
20
+ :class="`px-3 py-2 text-sm text-white cursor-pointer rounded-md ${i === 0 ? '' : 'my-2'}`"
21
21
  @click="set(country)">
22
- <div class="flex justify-center items-center smooth-hover">
22
+ <div class="flex items-center justify-center gap-3 smooth-hover">
23
23
  <span class="text-sm">{{ country }}</span>
24
24
  <img class="w-5 ml-3" :src="`/flags/${country?.toLowerCase()}.svg`" />
25
25
  </div>
@@ -40,9 +40,18 @@ const open = ref(false);
40
40
  const dropdownRef = ref(null);
41
41
 
42
42
  const props = defineProps({
43
- value: { type: String },
44
- onClick: { type: Function },
45
- disabled: { type: Boolean, required: false }
43
+ value: {
44
+ type: String,
45
+ default: 'US'
46
+ },
47
+ onClick: {
48
+ type: Function,
49
+ required: true
50
+ },
51
+ disabled: {
52
+ type: Boolean,
53
+ default: false
54
+ }
46
55
  });
47
56
 
48
57
  const current = ref(props.value || "US");
@@ -82,56 +91,3 @@ watch(
82
91
  (n) => (current.value = n)
83
92
  );
84
93
  </script>
85
-
86
- <style scoped>
87
- .special-dropdown {
88
- @apply min-w-20;
89
- }
90
-
91
- .small-dropdown {
92
- @apply h-10 !important;
93
- background-clip: border-box !important;
94
- /* border-radius: 100% !important; */
95
- padding: 0;
96
- width: 3em !important;
97
- display: flex;
98
- justify-items: center;
99
- justify-content: center;
100
- }
101
-
102
- @media (min-width: 768px) {
103
- .small-dropdown {
104
- height: 40px !important; /* Match input-default responsive height */
105
- }
106
- }
107
-
108
- .dropdown-content-portal {
109
- @apply bg-dark-400 border border-dark-650 rounded-lg shadow-2xl z-50;
110
- padding: 0.5rem;
111
- max-height: 192px !important;
112
- overflow-y: auto !important;
113
- overscroll-behavior: contain !important;
114
- touch-action: pan-y !important;
115
- -webkit-overflow-scrolling: touch !important;
116
- scrollbar-width: none;
117
- -ms-overflow-style: none;
118
- }
119
-
120
- .dropdown-content-portal::-webkit-scrollbar {
121
- display: none;
122
- }
123
-
124
- .dropdown-content-portal > div {
125
- @apply px-3 py-2 text-sm text-white cursor-pointer;
126
- border-radius: 6px;
127
- }
128
-
129
- .dropdown-content-portal > div:hover {
130
- /* Removed hover background */
131
- }
132
-
133
- .dropdown-content-portal > div .flex {
134
- @apply items-center justify-center;
135
- gap: 0.75rem;
136
- }
137
- </style>