@necrolab/dashboard 0.4.221 → 0.5.2

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 (141) hide show
  1. package/.prettierrc +27 -1
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +64 -2
  4. package/artwork/image.png +0 -0
  5. package/backend/api.js +26 -24
  6. package/backend/auth.js +2 -2
  7. package/backend/batching.js +1 -1
  8. package/backend/endpoints.js +8 -11
  9. package/backend/index.js +2 -2
  10. package/backend/mock-data.js +27 -36
  11. package/backend/mock-src/classes/logger.js +5 -7
  12. package/backend/mock-src/classes/utils.js +3 -2
  13. package/backend/mock-src/ticketmaster.js +4 -4
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +0 -1
  16. package/dev-server.js +134 -0
  17. package/exit +209 -0
  18. package/index.html +80 -8
  19. package/index.js +1 -1
  20. package/jsconfig.json +16 -0
  21. package/package.json +39 -25
  22. package/postcss.config.js +1 -1
  23. package/postinstall.js +124 -20
  24. package/public/android-chrome-192x192.png +0 -0
  25. package/public/android-chrome-512x512.png +0 -0
  26. package/public/apple-touch-icon.png +0 -0
  27. package/public/favicon-16x16.png +0 -0
  28. package/public/favicon-32x32.png +0 -0
  29. package/public/favicon.ico +0 -0
  30. package/public/img/logo_trans.png +0 -0
  31. package/public/img/necro_logo.png +0 -0
  32. package/public/manifest.json +16 -10
  33. package/public/reconnect-logo.png +0 -0
  34. package/run +176 -9
  35. package/src/App.vue +498 -85
  36. package/src/assets/css/base/reset.scss +43 -0
  37. package/src/assets/css/base/scroll.scss +114 -0
  38. package/src/assets/css/base/typography.scss +37 -0
  39. package/src/assets/css/components/buttons.scss +216 -0
  40. package/src/assets/css/components/forms.scss +221 -0
  41. package/src/assets/css/components/modals.scss +13 -0
  42. package/src/assets/css/components/tables.scss +27 -0
  43. package/src/assets/css/components/toasts.scss +100 -0
  44. package/src/assets/css/main.scss +202 -122
  45. package/src/assets/img/background.svg +2 -2
  46. package/src/assets/img/background.svg.backup +11 -0
  47. package/src/assets/img/logo_trans.png +0 -0
  48. package/src/components/Auth/LoginForm.vue +95 -11
  49. package/src/components/Editors/Account/Account.vue +116 -40
  50. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  51. package/src/components/Editors/Account/AccountView.vue +102 -34
  52. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  53. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  54. package/src/components/Editors/Profile/Profile.vue +132 -47
  55. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  56. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  57. package/src/components/Editors/TagLabel.vue +67 -6
  58. package/src/components/Editors/TagToggle.vue +7 -2
  59. package/src/components/Filter/Filter.vue +288 -71
  60. package/src/components/Filter/FilterPreview.vue +202 -31
  61. package/src/components/Filter/PriceSortToggle.vue +76 -6
  62. package/src/components/Table/Header.vue +1 -1
  63. package/src/components/Table/Row.vue +1 -1
  64. package/src/components/Table/Table.vue +19 -2
  65. package/src/components/Tasks/CheckStock.vue +6 -8
  66. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  67. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  68. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  69. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  70. package/src/components/Tasks/MassEdit.vue +4 -6
  71. package/src/components/Tasks/QuickSettings.vue +199 -30
  72. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  73. package/src/components/Tasks/Stats.vue +50 -24
  74. package/src/components/Tasks/Task.vue +384 -179
  75. package/src/components/Tasks/TaskLabel.vue +2 -2
  76. package/src/components/Tasks/TaskView.vue +136 -48
  77. package/src/components/Tasks/Utilities.vue +25 -10
  78. package/src/components/Tasks/ViewTask.vue +321 -0
  79. package/src/components/icons/Bag.vue +1 -1
  80. package/src/components/icons/Check.vue +5 -0
  81. package/src/components/icons/Close.vue +21 -0
  82. package/src/components/icons/CloseX.vue +5 -0
  83. package/src/components/icons/Eye.vue +6 -0
  84. package/src/components/icons/Key.vue +21 -0
  85. package/src/components/icons/Loyalty.vue +1 -1
  86. package/src/components/icons/Mail.vue +2 -2
  87. package/src/components/icons/Pencil.vue +21 -0
  88. package/src/components/icons/Play.vue +2 -2
  89. package/src/components/icons/Profile.vue +18 -0
  90. package/src/components/icons/Reload.vue +4 -5
  91. package/src/components/icons/Sandclock.vue +2 -2
  92. package/src/components/icons/Sell.vue +21 -0
  93. package/src/components/icons/Spinner.vue +42 -0
  94. package/src/components/icons/SquareCheck.vue +18 -0
  95. package/src/components/icons/SquareUncheck.vue +18 -0
  96. package/src/components/icons/Stadium.vue +1 -1
  97. package/src/components/icons/Wildcard.vue +18 -0
  98. package/src/components/icons/index.js +26 -1
  99. package/src/components/ui/Modal.vue +107 -13
  100. package/src/components/ui/Navbar.vue +175 -40
  101. package/src/components/ui/ReconnectIndicator.vue +351 -55
  102. package/src/components/ui/Splash.vue +5 -35
  103. package/src/components/ui/controls/CountryChooser.vue +200 -62
  104. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  105. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  106. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  107. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  108. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  109. package/src/composables/useClickOutside.js +21 -0
  110. package/src/composables/useDropdownPosition.js +174 -0
  111. package/src/libs/Filter.js +60 -24
  112. package/src/registerServiceWorker.js +1 -1
  113. package/src/stores/connection.js +4 -4
  114. package/src/stores/sampleData.js +172 -199
  115. package/src/stores/ui.js +55 -20
  116. package/src/stores/utils.js +30 -4
  117. package/src/types/index.js +41 -0
  118. package/src/utils/debug.js +1 -0
  119. package/src/views/Accounts.vue +116 -50
  120. package/src/views/Console.vue +394 -77
  121. package/src/views/Editor.vue +1176 -123
  122. package/src/views/FilterBuilder.vue +528 -250
  123. package/src/views/Login.vue +75 -14
  124. package/src/views/Profiles.vue +119 -34
  125. package/src/views/Tasks.vue +266 -98
  126. package/static/offline.html +192 -50
  127. package/switch-branch.sh +41 -0
  128. package/tailwind.config.js +119 -27
  129. package/vite.config.js +73 -16
  130. package/workbox-config.cjs +63 -0
  131. package/ICONS.md +0 -21
  132. package/public/img/background.svg +0 -14
  133. package/public/img/logo.png +0 -0
  134. package/public/img/logo_icon.png +0 -0
  135. package/public/img/logo_icon_2.png +0 -0
  136. package/src/assets/css/_input.scss +0 -143
  137. package/src/assets/img/logo.png +0 -0
  138. package/src/assets/img/logo_icon.png +0 -0
  139. package/src/assets/img/logo_icon_2.png +0 -0
  140. package/vue.config.js +0 -32
  141. package/workbox-config.js +0 -7
@@ -7,42 +7,71 @@
7
7
  </template>
8
8
 
9
9
  <div>
10
- <div class="my-3 grid grid-cols-12 gap-3 mt-7 mb-4">
11
- <div class="input-wrapper col-span-4 z-10">
12
- <label class="label-override">Profile Tag </label>
13
- <div :class="`input-default ${errors.includes('profileTag') ? 'error' : ''}`">
14
- <Dropdown
15
- class="text-xs w-64 h-8"
16
- :default="ui.profile.accountTags[0]"
17
- :options="ui.profile.accountTags"
18
- :onClick="(f) => (account.tag = f)"
19
- :capitalize="true"
20
- rightAmount="right-6 lg:right-2 top-2"
21
- />
22
- </div>
10
+ <div class="my-3 grid grid-cols-12 gap-3">
11
+ <!-- Account tag -->
12
+ <div class="input-wrapper relative-positioned z-tooltip col-span-4">
13
+ <label class="label-override mb-2">
14
+ Account Tag
15
+ <TagIcon />
16
+ </label>
17
+ <Dropdown
18
+ :class="`input-default dropdown w-full p-4`"
19
+ :default="ui.profile.tags[0]"
20
+ :options="ui.profile.tags"
21
+ :onClick="(f) => (account.tag = f)"
22
+ :capitalize="true"
23
+ :allowDefault="false"
24
+ :chosen="account.tag" />
23
25
  </div>
24
- <div class="input-wrapper col-span-8">
25
- <label class="label-override">Email </label>
26
- <div :class="`input-default ${errors.includes('email') ? 'error' : ''}`">
27
- <input placeholder="email@example.com" type="email" v-model="account.email" />
28
- <span class="text-red-400 my-auto mr-2 mt-3">* </span>
26
+
27
+ <!-- Email -->
28
+ <div class="input-wrapper z-0 col-span-8">
29
+ <label class="label-override mb-2">
30
+ Email
31
+ <MailIcon />
32
+ </label>
33
+ <div :class="`input-default required ${errors.includes('email') ? 'error' : ''}`">
34
+ <input
35
+ placeholder="email@example.com"
36
+ type="email"
37
+ v-model="account.email"
38
+ required
39
+ autocomplete="new-password"
40
+ name="not-email-field-random-12345"
41
+ data-dashlane-rid=""
42
+ data-dashlane-label=""
43
+ data-dashlane-classification=""
44
+ data-form-type="other"
45
+ role="textbox"
46
+ inputmode="email" />
29
47
  </div>
30
48
  </div>
31
- </div>
32
- <div class="input-wrapper col-span-12">
33
- <label class="label-override">Password </label>
34
- <div :class="`input-default ${errors.includes('password') ? 'error' : ''}`">
35
- <input placeholder="***********" v-model="account.password" />
36
- <span class="text-red-400 my-auto mr-2 mt-3">* </span>
49
+
50
+ <!-- Password -->
51
+ <div class="input-wrapper z-0 col-span-12">
52
+ <label class="label-override mb-2">
53
+ Password
54
+ <KeyIcon />
55
+ </label>
56
+ <div :class="`input-default required ${errors.includes('password') ? 'error' : ''}`">
57
+ <input
58
+ placeholder="***********"
59
+ type="password"
60
+ v-model="account.password"
61
+ required
62
+ autocomplete="off"
63
+ name="account_password_disableautocomplete" />
64
+ </div>
37
65
  </div>
38
66
  </div>
39
- <button
40
- class="button-default hover:opacity-70 active:opacity-50 bg-dark-400 w-48 text-xs flex items-center justify-center gap-x-2 ml-auto mt-4"
41
- @click="done()"
42
- >
43
- Save <EditIcon />
44
- </button>
45
67
  </div>
68
+
69
+ <button
70
+ class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
71
+ @click="done()">
72
+ Save
73
+ <EditIcon />
74
+ </button>
46
75
  </Modal>
47
76
  </template>
48
77
  <style lang="scss" scoped>
@@ -51,6 +80,16 @@
51
80
  @apply flex;
52
81
  }
53
82
  }
83
+ .z-0 {
84
+ z-index: 0 !important;
85
+ }
86
+ .z-1 {
87
+ z-index: 1 !important;
88
+ }
89
+ .z-2 {
90
+ z-index: 2 !important;
91
+ }
92
+
54
93
  .error {
55
94
  border-width: 2px !important;
56
95
  border-color: rgb(238 130 130) !important;
@@ -58,7 +97,16 @@
58
97
  </style>
59
98
  <script setup>
60
99
  import Modal from "@/components/ui/Modal.vue";
61
- import { EditIcon } from "@/components/icons";
100
+ import {
101
+ EditIcon,
102
+ MailIcon,
103
+ KeyIcon,
104
+ ProfileIcon,
105
+ TimerIcon,
106
+ SandclockIcon,
107
+ TagIcon,
108
+ ScannerIcon
109
+ } from "@/components/icons";
62
110
  import { useUIStore } from "@/stores/ui";
63
111
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
64
112
 
@@ -68,7 +116,7 @@ const ui = useUIStore();
68
116
  const account = ref({
69
117
  email: "",
70
118
  password: "",
71
- tag: ui.profile.accountTags[0]
119
+ tag: ui.profile.tags[0]
72
120
  });
73
121
 
74
122
  if (ui.currentlyEditing?.email) account.value = ui.currentlyEditing;
@@ -7,88 +7,92 @@
7
7
  </template>
8
8
 
9
9
  <div>
10
- <div class="grid grid-cols-12 gap-3 my-3">
10
+ <div class="my-3 grid grid-cols-12 gap-3">
11
11
  <!-- Profile tag -->
12
- <div class="input-wrapper col-span-4" style="z-index: 30 !important">
13
- <label class="label-override mb-2">Profile Tag </label>
14
- <div :class="`input-default ${errors.includes('profileTag') ? 'error' : ''}`">
15
- <Dropdown
16
- class="text-xs w-64 h-8"
17
- :default="ui.profile.accountTags[0]"
18
- :options="ui.profile.accountTags"
19
- :onClick="(f) => (profile.tag = f)"
20
- :capitalize="true"
21
- rightAmount="right-6 md:right-2 top-2"
22
- />
23
- </div>
12
+ <div class="input-wrapper z-dropdown col-span-4">
13
+ <label class="label-override mb-2">
14
+ Profile Tag
15
+ <TagIcon />
16
+ </label>
17
+ <Dropdown
18
+ :class="`input-default dropdown w-full ${errors.includes('profileTag') ? 'error' : ''}`"
19
+ :default="ui.profile.tags[0]"
20
+ :options="ui.profile.tags"
21
+ :onClick="(f) => (profile.tag = f)"
22
+ :capitalize="true" />
24
23
  </div>
25
24
 
26
25
  <!-- Card Number -->
27
- <div class="input-wrapper col-span-8 z-0">
28
- <label class="label-override mb-2">Card Number </label>
26
+ <div class="input-wrapper z-0 col-span-8">
27
+ <label class="label-override mb-2">
28
+ Card Number
29
+ <CartIcon />
30
+ </label>
29
31
  <div :class="`input-default ${errors.includes('cardNumber') ? 'error' : ''}`">
30
32
  <input
33
+ ref="cardNumberInput"
31
34
  placeholder="Enter card number"
32
- v-model="profile.cardNumber"
33
- maxlength="19"
35
+ v-model="displayCardNumber"
36
+ maxlength="23"
34
37
  inputmode="numeric"
35
38
  @input="handleCreditCardUpdate"
36
- />
39
+ @focus="formatCardNumberDisplay" />
37
40
  </div>
38
41
  </div>
39
42
 
40
43
  <!-- Country chooser -->
41
44
  <div class="input-wrapper col-span-2">
42
- <label class="label-override mb-2">Country </label>
45
+ <label class="label-override mb-2">
46
+ Country
47
+ <SandclockIcon />
48
+ </label>
43
49
  <ProfileCountryChooser
44
50
  class="h-8"
45
51
  :value="profile.country"
46
52
  :onClick="chooseCountry"
47
- :disabled="true"
48
- />
53
+ :disabled="true" />
49
54
  </div>
50
55
 
51
56
  <!-- Exp Year -->
52
57
  <div class="input-wrapper col-span-5">
53
- <label class="label-override mb-2 z-0">Expiry Year </label>
54
- <div :class="`input-default ${errors.includes('expYear') ? 'error' : ''}`">
55
- <Dropdown
56
- class="text-xs w-64 h-8"
57
- default="Expiry Year"
58
- :value="
59
- profile.expYear && !profile?.expYear?.startsWith('20')
60
- ? '20' + profile.expYear
61
- : profile.expYear
62
- ? profile.expYear
63
- : undefined
64
- "
65
- :options="['2025', '2026', '2027', '2028', '2029', '2030', '2031']"
66
- :onClick="(f) => (profile.expYear = f)"
67
- rightAmount="right-2 top-2"
68
- topPadding="pt-2 mt-2"
69
- />
70
- </div>
58
+ <label class="label-override z-0 mb-2">
59
+ Expiry Year
60
+ <TimerIcon />
61
+ </label>
62
+ <Dropdown
63
+ :class="`input-default dropdown w-full ${errors.includes('expYear') ? 'error' : ''}`"
64
+ default="Expiry Year"
65
+ :value="
66
+ profile.expYear && !profile?.expYear?.startsWith('20')
67
+ ? '20' + profile.expYear
68
+ : profile.expYear
69
+ ? profile.expYear
70
+ : undefined
71
+ "
72
+ :options="['2025', '2026', '2027', '2028', '2029', '2030', '2031']"
73
+ :onClick="(f) => (profile.expYear = f)" />
71
74
  </div>
72
75
 
73
76
  <!-- Exp Month -->
74
- <div class="input-wrapper col-span-5">
75
- <label class="label-override mb-2">Expiry Month </label>
76
- <div :class="`input-default ${errors.includes('expMonth') ? 'error' : ''}`">
77
- <Dropdown
78
- class="text-xs w-64 h-8"
79
- default="Expiry Month"
80
- :value="profile.expMonth ? profile.expMonth : undefined"
81
- :options="['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']"
82
- :onClick="(f) => (profile.expMonth = f)"
83
- rightAmount="right-2 top-2"
84
- topPadding="pt-2 mt-2"
85
- />
86
- </div>
77
+ <div class="input-wrapper z-2 col-span-5">
78
+ <label class="label-override mb-2">
79
+ Expiry Month
80
+ <TimerIcon />
81
+ </label>
82
+ <Dropdown
83
+ :class="`input-default dropdown w-full ${errors.includes('expMonth') ? 'error' : ''}`"
84
+ default="Expiry Month"
85
+ :value="profile.expMonth ? profile.expMonth : undefined"
86
+ :options="['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']"
87
+ :onClick="(f) => (profile.expMonth = f)" />
87
88
  </div>
88
89
 
89
90
  <!-- CVV -->
90
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
91
- <label class="label-override mb-2">CVV </label>
91
+ <div class="input-wrapper z-0 col-span-6 md:col-span-4">
92
+ <label class="label-override mb-2">
93
+ CVV
94
+ <ShieldIcon />
95
+ </label>
92
96
  <div :class="`input-default ${errors.includes('cvv') ? 'error' : ''}`">
93
97
  <input
94
98
  placeholder="183"
@@ -96,51 +100,88 @@
96
100
  max="9999"
97
101
  maxlength="4"
98
102
  minlength="3"
99
- v-model="profile.cvv"
100
- />
103
+ v-model="profile.cvv" />
101
104
  </div>
102
105
  </div>
103
106
 
104
107
  <!-- City -->
105
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
106
- <label class="label-override mb-2">City </label>
108
+ <div class="input-wrapper z-0 col-span-6 md:col-span-4">
109
+ <label class="label-override mb-2">
110
+ City
111
+ <StadiumIcon />
112
+ </label>
107
113
  <div :class="`input-default ${errors.includes('city') ? 'error' : ''}`">
108
114
  <input placeholder="Denver" v-model="profile.city" />
109
115
  </div>
110
116
  </div>
111
117
 
112
118
  <!-- State -->
113
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
114
- <label class="label-override mb-2">State </label>
115
- <div :class="`input-default ${errors.includes('state') ? 'error' : ''}`">
116
- <input :disabled="profile.country !== 'US'" placeholder="CO" v-model="profile.state" />
119
+ <div class="input-wrapper z-1 col-span-6 md:col-span-4">
120
+ <label class="label-override mb-2">
121
+ State
122
+ <SandclockIcon />
123
+ </label>
124
+ <div v-if="profile.country === 'US'" :class="`${errors.includes('state') ? 'error' : ''}`">
125
+ <Dropdown
126
+ class="input-default w-full"
127
+ default="Select State"
128
+ :onClick="(state) => (profile.state = state)"
129
+ :options="usStates"
130
+ :allowDefault="false"
131
+ rightAmount="right-2"
132
+ :value="profile.state" />
133
+ </div>
134
+ <div v-else :class="`input-default ${errors.includes('state') ? 'error' : ''}`">
135
+ <input disabled placeholder="N/A" value="" />
117
136
  </div>
118
137
  </div>
119
138
 
120
139
  <!-- Zip -->
121
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
122
- <label class="label-override mb-2">Zip </label>
140
+ <div class="input-wrapper z-0 col-span-6 md:col-span-4">
141
+ <label class="label-override mb-2">
142
+ Zip
143
+ <KeyIcon />
144
+ </label>
123
145
  <div :class="`input-default ${errors.includes('zipCode') ? 'error' : ''}`">
124
- <input placeholder="80281" type="number" min="1" max="12" v-model="profile.zipCode" />
146
+ <input placeholder="10005" type="number" min="1" max="12" v-model="profile.zipCode" />
125
147
  </div>
126
148
  </div>
127
149
 
128
150
  <!-- Address -->
129
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
130
- <label class="label-override mb-2">Address </label>
151
+ <div class="input-wrapper z-0 col-span-6 md:col-span-4">
152
+ <label class="label-override mb-2">
153
+ Address
154
+ <HandIcon />
155
+ </label>
131
156
  <div :class="`input-default ${errors.includes('address') ? 'error' : ''}`">
132
- <input placeholder="Denver" v-model="profile.address" />
157
+ <input placeholder="100 5th Avenue" v-model="profile.address" />
133
158
  </div>
134
159
  </div>
135
160
 
136
161
  <!-- Generate -->
137
- <div class="input-wrapper col-span-6 md:col-span-4 z-0">
138
- <label class="label-override mb-2 z-0"> Fake ID</label>
139
- <div class="input-default mt-2 w-full">
162
+ <div class="input-wrapper z-0 col-span-6 md:col-span-4">
163
+ <label class="label-override mb-2">
164
+ Fake ID
165
+ <WildcardIcon />
166
+ </label>
167
+ <div class="input-default mt-2 flex h-10 w-full items-center">
140
168
  <button
141
169
  @click="generate"
142
- class="text-white w-full button-default text-xs flex items-center justify-center"
143
- >
170
+ class="flex w-full items-center justify-center gap-2 text-xs text-white">
171
+ <svg
172
+ xmlns="http://www.w3.org/2000/svg"
173
+ width="14"
174
+ height="14"
175
+ viewBox="0 0 24 24"
176
+ fill="none"
177
+ stroke="currentColor"
178
+ stroke-width="2"
179
+ stroke-linecap="round"
180
+ stroke-linejoin="round">
181
+ <rect x="3" y="4" width="18" height="16" rx="2" />
182
+ <line x1="7" y1="2" x2="7" y2="6" />
183
+ <line x1="17" y1="2" x2="17" y2="6" />
184
+ </svg>
144
185
  <span>Generate</span>
145
186
  </button>
146
187
  </div>
@@ -149,10 +190,10 @@
149
190
  </div>
150
191
 
151
192
  <button
152
- class="button-default hover:opacity-70 active:opacity-50 bg-dark-400 w-48 text-xs flex items-center justify-center gap-x-2 ml-auto mt-4"
153
- @click="done()"
154
- >
155
- Save <EditIcon />
193
+ class="button-default ml-auto mt-4 flex w-48 items-center justify-center gap-x-2 bg-dark-400 text-xs"
194
+ @click="done()">
195
+ Save
196
+ <EditIcon />
156
197
  </button>
157
198
  </Modal>
158
199
  </template>
@@ -165,6 +206,12 @@
165
206
  .z-0 {
166
207
  z-index: 0 !important;
167
208
  }
209
+ .z-1 {
210
+ z-index: 1 !important;
211
+ }
212
+ .z-2 {
213
+ z-index: 2 !important;
214
+ }
168
215
 
169
216
  .error {
170
217
  border-width: 2px !important;
@@ -173,22 +220,89 @@
173
220
  </style>
174
221
  <script setup>
175
222
  import Modal from "@/components/ui/Modal.vue";
223
+ import {
224
+ MailIcon,
225
+ CartIcon,
226
+ ShieldIcon,
227
+ StadiumIcon,
228
+ KeyIcon,
229
+ HandIcon,
230
+ ProfileIcon,
231
+ SandclockIcon,
232
+ TimerIcon,
233
+ TagIcon,
234
+ WildcardIcon
235
+ } from "@/components/icons";
176
236
  import { EditIcon } from "@/components/icons";
177
- import ProfileCountryChooser from "@/components/editors/Profile/ProfileCountryChooser.vue";
237
+ import ProfileCountryChooser from "@/components/Editors/Profile/ProfileCountryChooser.vue";
178
238
  import { useUIStore } from "@/stores/ui";
179
239
  import Dropdown from "@/components/ui/controls/atomic/Dropdown.vue";
180
- import { ref } from "vue";
240
+ import { ref, computed, watch, nextTick, onMounted } from "vue";
181
241
  import { fakeId } from "@/stores/utils";
182
242
  import { validateCard } from "@/stores/utils";
183
243
 
184
244
  const props = defineProps({ profile: { type: Object, required: false } });
185
245
  const errors = ref([]);
186
246
  const ui = useUIStore();
247
+
248
+ // US States list (excluding GA and PR, alphabetically ordered)
249
+ const usStates = [
250
+ "AK",
251
+ "AL",
252
+ "AR",
253
+ "AZ",
254
+ "CA",
255
+ "CO",
256
+ "CT",
257
+ "DE",
258
+ "FL",
259
+ "HI",
260
+ "IA",
261
+ "ID",
262
+ "IL",
263
+ "IN",
264
+ "KS",
265
+ "KY",
266
+ "LA",
267
+ "MA",
268
+ "MD",
269
+ "ME",
270
+ "MI",
271
+ "MN",
272
+ "MO",
273
+ "MS",
274
+ "MT",
275
+ "NC",
276
+ "ND",
277
+ "NE",
278
+ "NH",
279
+ "NJ",
280
+ "NM",
281
+ "NV",
282
+ "NY",
283
+ "OH",
284
+ "OK",
285
+ "OR",
286
+ "PA",
287
+ "RI",
288
+ "SC",
289
+ "SD",
290
+ "TN",
291
+ "TX",
292
+ "UT",
293
+ "VA",
294
+ "VT",
295
+ "WA",
296
+ "WI",
297
+ "WV",
298
+ "WY"
299
+ ];
300
+ const cardNumberInput = ref(null);
187
301
  const profile = ref({
188
302
  cvv: "",
189
303
  cardNumber: "",
190
304
  city: "",
191
- tag: ui.profile.accountTags[0],
305
+ tag: ui.profile.tags[0],
192
306
  state: "",
193
307
  country: "US",
194
308
  zipCode: ""
@@ -196,6 +310,54 @@ const profile = ref({
196
310
 
197
311
  if (ui.currentlyEditing?.profileName) profile.value = ui.currentlyEditing;
198
312
 
313
+ // Reactive display value for the formatted card number
314
+ const displayCardNumber = ref("");
315
+
316
+ // Initialize display card number with formatting
317
+ const initializeCardNumberDisplay = () => {
318
+ if (profile.value.cardNumber) {
319
+ const cleanNumber = profile.value.cardNumber.replace(/\s+/g, "");
320
+ if (cleanNumber) {
321
+ const cardInfo = validateCard(cleanNumber);
322
+ displayCardNumber.value = cardInfo.formatted;
323
+ }
324
+ }
325
+ };
326
+
327
+ // Format card number display on focus
328
+ const formatCardNumberDisplay = () => {
329
+ if (profile.value.cardNumber) {
330
+ const cleanNumber = profile.value.cardNumber.replace(/\s+/g, "");
331
+ if (cleanNumber) {
332
+ const cardInfo = validateCard(cleanNumber);
333
+ displayCardNumber.value = cardInfo.formatted;
334
+ }
335
+ }
336
+ };
337
+
338
+ // Watch for changes in profile.cardNumber to sync with display
339
+ watch(
340
+ () => profile.value.cardNumber,
341
+ (newValue) => {
342
+ if (newValue) {
343
+ const cleanNumber = newValue.replace(/\s+/g, "");
344
+ if (cleanNumber) {
345
+ const cardInfo = validateCard(cleanNumber);
346
+ displayCardNumber.value = cardInfo.formatted;
347
+ }
348
+ } else {
349
+ displayCardNumber.value = "";
350
+ }
351
+ }
352
+ );
353
+
354
+ // Initialize on modal open
355
+ onMounted(() => {
356
+ nextTick(() => {
357
+ initializeCardNumberDisplay();
358
+ });
359
+ });
360
+
199
361
  const generate = () => {
200
362
  const fake = fakeId();
201
363
  profile.value.city = fake.city;
@@ -220,7 +382,13 @@ const validate = (p) => {
220
382
  if (!p.city) errors.value.push("city");
221
383
  if (!p.country) errors.value.push("country");
222
384
  if (!/^\d{3,4}$/.test(`${p.cvv}`)) errors.value.push("cvv");
223
- if (!/^[345]\d{14,15}$/.test(p.cardNumber)) errors.value.push("cardNumber");
385
+ const cleanCardNumber = p.cardNumber.replace(/\s+/g, "");
386
+ // Validate card number based on type and length
387
+ const isValidCard =
388
+ cleanCardNumber.match(/^4\d{15}$/) || // Visa (16 digits)
389
+ cleanCardNumber.match(/^5[1-5]\d{14}$/) || // Mastercard (16 digits)
390
+ cleanCardNumber.match(/^3[47]\d{13}$/); // AMEX (15 digits)
391
+ if (!isValidCard) errors.value.push("cardNumber");
224
392
  if (!p.expYear) errors.value.push("expYear");
225
393
  if (!p.expMonth) errors.value.push("expMonth");
226
394
  if (!p.expMonth) errors.value.push("expMonth");
@@ -229,12 +397,30 @@ const validate = (p) => {
229
397
 
230
398
  const handleCreditCardUpdate = (event) => {
231
399
  const value = event.target.value.replace(/\D/g, "");
232
- const subs = value.substring(0, 16);
400
+
401
+ // Determine max length based on card type
402
+ let maxLength = 16; // Default for Visa/Mastercard
403
+ if (value.startsWith("3")) {
404
+ maxLength = 15; // AMEX
405
+ } else if (value.startsWith("4") || value.startsWith("5")) {
406
+ maxLength = 16; // Visa/Mastercard
407
+ }
408
+
409
+ const subs = value.substring(0, maxLength);
233
410
  const cardInfo = validateCard(subs);
234
- profile.value.cardNumber = cardInfo.formatted;
411
+
412
+ // Store clean number (without spaces) in profile
413
+ profile.value.cardNumber = subs;
414
+ // Display formatted number in input
415
+ displayCardNumber.value = cardInfo.formatted;
235
416
  };
236
417
 
237
418
  function done() {
419
+ // Clear state if country is not US
420
+ if (profile.value.country !== "US") {
421
+ profile.value.state = "";
422
+ }
423
+
238
424
  ui.logger.Info("Created profile", profile.value);
239
425
  if (validate(profile.value) !== true) return;
240
426
  ui.toggleModal("");