@edgedev/create-edge-app 1.1.23 → 1.1.26

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 (116) hide show
  1. package/.env +1 -0
  2. package/.env.dev +1 -0
  3. package/README.md +55 -20
  4. package/{agent.md → agents.md} +2 -0
  5. package/bin/cli.js +6 -6
  6. package/edge/components/auth/login.vue +384 -0
  7. package/edge/components/auth/register.vue +396 -0
  8. package/edge/components/auth.vue +108 -0
  9. package/edge/components/autoFileUpload.vue +215 -0
  10. package/edge/components/billing.vue +8 -0
  11. package/edge/components/buttonDivider.vue +14 -0
  12. package/edge/components/chip.vue +34 -0
  13. package/edge/components/clipboardButton.vue +42 -0
  14. package/edge/components/cms/block.vue +529 -0
  15. package/edge/components/cms/blockApi.vue +212 -0
  16. package/edge/components/cms/blockEditor.vue +725 -0
  17. package/edge/components/cms/blockInput.vue +66 -0
  18. package/edge/components/cms/blockPicker.vue +486 -0
  19. package/edge/components/cms/blockRender.vue +78 -0
  20. package/edge/components/cms/blockSheetContent.vue +28 -0
  21. package/edge/components/cms/codeEditor.vue +466 -0
  22. package/edge/components/cms/fontUpload.vue +327 -0
  23. package/edge/components/cms/htmlContent.vue +807 -0
  24. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  25. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  26. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  27. package/edge/components/cms/init_blocks/carousel.html +103 -0
  28. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  29. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  30. package/edge/components/cms/init_blocks/footer.html +24 -0
  31. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  32. package/edge/components/cms/init_blocks/hero.html +35 -0
  33. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  34. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  35. package/edge/components/cms/init_blocks/post_content.html +7 -0
  36. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  37. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  38. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  39. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  40. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  41. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  42. package/edge/components/cms/init_blocks/property_results.html +137 -0
  43. package/edge/components/cms/init_blocks/property_search.html +75 -0
  44. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  45. package/edge/components/cms/mediaCard.vue +116 -0
  46. package/edge/components/cms/mediaManager.vue +386 -0
  47. package/edge/components/cms/menu.vue +1103 -0
  48. package/edge/components/cms/optionsSelect.vue +107 -0
  49. package/edge/components/cms/page.vue +1785 -0
  50. package/edge/components/cms/posts.vue +1083 -0
  51. package/edge/components/cms/site.vue +1298 -0
  52. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  53. package/edge/components/cms/themeEditor.vue +426 -0
  54. package/edge/components/dashboard.vue +776 -0
  55. package/edge/components/editor.vue +671 -0
  56. package/edge/components/fileTree.vue +72 -0
  57. package/edge/components/files.vue +89 -0
  58. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  59. package/edge/components/formSubtypes/users.vue +336 -0
  60. package/edge/components/functionChips.vue +57 -0
  61. package/edge/components/gError.vue +98 -0
  62. package/edge/components/gHelper.vue +67 -0
  63. package/edge/components/gInput.vue +1331 -0
  64. package/edge/components/loggingIn.vue +41 -0
  65. package/edge/components/menu.vue +137 -0
  66. package/edge/components/menuContent.vue +132 -0
  67. package/edge/components/myAccount.vue +317 -0
  68. package/edge/components/myOrganizations.vue +75 -0
  69. package/edge/components/myProfile.vue +122 -0
  70. package/edge/components/orgSwitcher.vue +25 -0
  71. package/edge/components/organizationMembers.vue +522 -0
  72. package/edge/components/organizationSettings.vue +271 -0
  73. package/edge/components/shad/breadcrumbs.vue +35 -0
  74. package/edge/components/shad/button.vue +43 -0
  75. package/edge/components/shad/checkbox.vue +73 -0
  76. package/edge/components/shad/combobox.vue +238 -0
  77. package/edge/components/shad/datepicker.vue +184 -0
  78. package/edge/components/shad/dialog.vue +32 -0
  79. package/edge/components/shad/dropdownMenu.vue +54 -0
  80. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  81. package/edge/components/shad/form.vue +59 -0
  82. package/edge/components/shad/html.vue +877 -0
  83. package/edge/components/shad/input.vue +139 -0
  84. package/edge/components/shad/number.vue +109 -0
  85. package/edge/components/shad/select.vue +151 -0
  86. package/edge/components/shad/selectTags.vue +278 -0
  87. package/edge/components/shad/switch.vue +67 -0
  88. package/edge/components/shad/tags.vue +137 -0
  89. package/edge/components/shad/textarea.vue +102 -0
  90. package/edge/components/shad/typeMoney.vue +167 -0
  91. package/edge/components/sideBar.vue +288 -0
  92. package/edge/components/sideBarContent.vue +268 -0
  93. package/edge/components/sidebarProvider.vue +33 -0
  94. package/edge/components/tooltip.vue +16 -0
  95. package/edge/components/userMenu.vue +148 -0
  96. package/edge/components/v/alert.vue +59 -0
  97. package/edge/components/v/alertTitle.vue +18 -0
  98. package/edge/components/v/card.vue +53 -0
  99. package/edge/components/v/cardActions.vue +18 -0
  100. package/edge/components/v/cardText.vue +18 -0
  101. package/edge/components/v/cardTitle.vue +20 -0
  102. package/edge/components/v/col.vue +56 -0
  103. package/edge/components/v/list.vue +46 -0
  104. package/edge/components/v/listItem.vue +26 -0
  105. package/edge/components/v/listItemTitle.vue +18 -0
  106. package/edge/components/v/row.vue +42 -0
  107. package/edge/components/v/toolbar.vue +24 -0
  108. package/edge/composables/global.ts +519 -0
  109. package/edge-pull.sh +2 -0
  110. package/edge-push.sh +1 -0
  111. package/edge-status.sh +14 -0
  112. package/firebase.json +5 -2
  113. package/firebase_init.sh +21 -6
  114. package/package.json +1 -1
  115. package/plugins/firebase.client.ts +1 -0
  116. package/edge-components-install.sh +0 -1
@@ -0,0 +1,336 @@
1
+ <script setup>
2
+ import { toTypedSchema } from '@vee-validate/zod'
3
+ import * as z from 'zod'
4
+ // TODO: If a removed user no longer has roles to any organiztions, need to a create new organization for them with
5
+ // default name of "Personal". This will allow them to continue to use the app.
6
+
7
+ // TODO: Finish user setup.
8
+ // TODO: Add error/success to join/add organization.
9
+ const props = defineProps({
10
+ item: {
11
+ type: Object,
12
+ default: null,
13
+ },
14
+ items: {
15
+ type: Array,
16
+ default: () => [],
17
+ },
18
+ passThroughProps: {
19
+ type: [Number, String, Array, Object, Boolean],
20
+ required: false,
21
+ },
22
+ })
23
+
24
+ const edgeFirebase = inject('edgeFirebase')
25
+ const state = reactive({
26
+ workingItem: {},
27
+ dialog: false,
28
+ form: false,
29
+ currentTitle: '',
30
+ saveButton: 'Invite User',
31
+ helpers: {
32
+ submits: true,
33
+ },
34
+ deleteDialog: false,
35
+ loading: false,
36
+ })
37
+
38
+ const roleNamesOnly = computed(() => {
39
+ return edgeGlobal.edgeState.userRoles.map((role) => {
40
+ return role.name
41
+ })
42
+ })
43
+
44
+ const newItem = {
45
+ name: '',
46
+ email: '',
47
+ role: '',
48
+ isTemplate: false,
49
+ }
50
+
51
+ // computed property gets count of admin users in organization
52
+ const adminCount = computed(() => {
53
+ return props.items.filter((item) => {
54
+ return item.roles.find((role) => {
55
+ return role.collectionPath === edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-') && role.role === 'admin'
56
+ })
57
+ }).length
58
+ })
59
+
60
+ const addItem = () => {
61
+ state.saveButton = 'Invite User'
62
+ state.workingItem = edgeGlobal.dupObject(newItem)
63
+ state.workingItem.id = edgeGlobal.generateShortId()
64
+ state.currentTitle = 'Invite User'
65
+ state.dialog = true
66
+ }
67
+
68
+ const editItem = (item) => {
69
+ state.currentTitle = item.name
70
+ state.saveButton = 'Update User'
71
+ state.workingItem = edgeGlobal.dupObject(item)
72
+ state.workingItem.name = item.meta.name
73
+ state.workingItem.role = edgeGlobal.getRoleName(props.item.roles, edgeGlobal.edgeState.currentOrganization)
74
+ const newItemKeys = Object.keys(newItem)
75
+ newItemKeys.forEach((key) => {
76
+ if (state.workingItem[key] === undefined) {
77
+ state.workingItem[key] = newItem[key]
78
+ }
79
+ })
80
+ state.dialog = true
81
+ }
82
+
83
+ const deleteConfirm = (item) => {
84
+ state.currentTitle = item.name
85
+ state.workingItem = edgeGlobal.dupObject(item)
86
+ state.deleteDialog = true
87
+ }
88
+
89
+ const deleteAction = async () => {
90
+ const userRoles = state.workingItem.roles.filter((role) => {
91
+ return role.collectionPath.startsWith(edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-'))
92
+ })
93
+ for (const role of userRoles) {
94
+ await edgeFirebase.removeUserRoles(state.workingItem.docId, role.collectionPath)
95
+ // console.log(role.collectionPath)
96
+ }
97
+ state.deleteDialog = false
98
+ edgeGlobal.edgeState.changeTracker = {}
99
+ }
100
+
101
+ const closeDialog = () => {
102
+ state.dialog = false
103
+ edgeGlobal.edgeState.changeTracker = {}
104
+ }
105
+
106
+ const disableTracking = computed(() => {
107
+ return state.saveButton === 'Invite User'
108
+ })
109
+
110
+ const onSubmit = async () => {
111
+ state.loading = true
112
+ const userRoles = edgeGlobal.orgUserRoles(edgeGlobal.edgeState.currentOrganization)
113
+ const roles = userRoles.find(role => role.name === state.workingItem.role).roles
114
+ if (state.saveButton === 'Invite User') {
115
+ if (!state.workingItem.isTemplate) {
116
+ await edgeFirebase.addUser({ roles, meta: { name: state.workingItem.name, email: state.workingItem.email } })
117
+ }
118
+ else {
119
+ await edgeFirebase.addUser({ roles, meta: { name: state.workingItem.name, email: state.workingItem.email }, isTemplate: true })
120
+ }
121
+ }
122
+ else {
123
+ const oldRoles = state.workingItem.roles.filter((role) => {
124
+ return role.collectionPath.startsWith(edgeGlobal.edgeState.organizationDocPath.replaceAll('/', '-'))
125
+ && !roles.find(r => r.collectionPath === role.collectionPath)
126
+ })
127
+
128
+ for (const role of oldRoles) {
129
+ await edgeFirebase.removeUserRoles(state.workingItem.docId, role.collectionPath)
130
+ }
131
+
132
+ for (const role of roles) {
133
+ await edgeFirebase.storeUserRoles(state.workingItem.docId, role.collectionPath, role.role)
134
+ }
135
+ }
136
+ edgeGlobal.edgeState.changeTracker = {}
137
+ state.loading = false
138
+ state.dialog = false
139
+ }
140
+
141
+ const newUserSchema = toTypedSchema(z.object({
142
+ name: z.string({
143
+ required_error: 'Name is required',
144
+ }).min(1, { message: 'Name is required' }),
145
+ email: z.string({
146
+ required_error: 'Email is required',
147
+ }).email({ message: 'Invalid email address' }).min(6, { message: 'Email must be at least 6 characters long' }).max(50, { message: 'Email must be less than 50 characters long' }),
148
+ role: z.string({
149
+ required_error: 'Role is required',
150
+ }).min(1, { message: 'Role is required' }),
151
+ }))
152
+
153
+ const updateUserSchema = toTypedSchema(z.object({
154
+ name: z.string({
155
+ required_error: 'Name is required',
156
+ }).min(1, { message: 'Name is required' }),
157
+ role: z.string({
158
+ required_error: 'Role is required',
159
+ }).min(1, { message: 'Role is required' }),
160
+ }))
161
+
162
+ const computedUserSchema = computed(() => {
163
+ if (state.saveButton === 'Invite User') {
164
+ return newUserSchema
165
+ }
166
+ return updateUserSchema
167
+ })
168
+
169
+ const currentOrganization = computed(() => {
170
+ if (edgeGlobal.edgeState.organizations.length > 0) {
171
+ if (edgeGlobal.edgeState.currentOrganization && edgeFirebase?.data[`organizations/${edgeGlobal.edgeState.currentOrganization}`]) {
172
+ return edgeFirebase?.data[`organizations/${edgeGlobal.edgeState.currentOrganization}`]
173
+ }
174
+ }
175
+ return ''
176
+ })
177
+ </script>
178
+
179
+ <template>
180
+ <edge-shad-button v-if="props.item === null" class="bg-slate-500 mx-2 h-6 text-xs" @click="addItem()">
181
+ Invite Member
182
+ </edge-shad-button>
183
+ <div v-else class="flex w-full py-2 justify-between items-center cursor-pointer" @click="editItem(props.item)">
184
+ <Avatar class="handle pointer p-0 h-6 w-6 mr-2">
185
+ <User width="18" height="18" />
186
+ </Avatar>
187
+ <div class="flex gap-2 mr-2 items-center">
188
+ <div class="text-md text-bold mr-2">
189
+ {{ props.item.meta.name }}
190
+ </div>
191
+ <edge-chip v-if="props.item.userId === edgeFirebase.user.uid">
192
+ You
193
+ </edge-chip>
194
+ <edge-chip v-if="!props.item.userId" class="bg-warning">
195
+ Invited, Not Registered
196
+ </edge-chip>
197
+ </div>
198
+ <div class="grow flex gap-2 justify-end">
199
+ <edge-chip>
200
+ {{ edgeGlobal.getRoleName(props.item.roles, edgeGlobal.edgeState.currentOrganization) }}
201
+ </edge-chip>
202
+ <template v-if="!props.item.userId">
203
+ <edge-chip>
204
+ Registration Code: {{ props.item.docId }}
205
+ <edge-clipboard-button :text="props.item.docId" />
206
+ </edge-chip>
207
+ </template>
208
+ </div>
209
+ <edge-shad-button
210
+ :disabled="items.length === 1"
211
+ class="bg-red-400 mx-2 h-6 text-xs"
212
+ variant="outline"
213
+ @click.stop="deleteConfirm(props.item)"
214
+ >
215
+ <span v-if="props.item.userId === edgeFirebase.user.uid">Leave</span>
216
+ <span v-else>Remove</span>
217
+ </edge-shad-button>
218
+ </div>
219
+ <edge-shad-dialog
220
+ v-model="state.deleteDialog"
221
+ >
222
+ <DialogContent>
223
+ <DialogHeader>
224
+ <DialogTitle>
225
+ <span v-if="state.workingItem.userId === edgeFirebase.user.uid">
226
+ Remove Yourself?
227
+ </span>
228
+ <span v-else>
229
+ Remove "{{ state.workingItem.meta.name }}"
230
+ </span>
231
+ </DialogTitle>
232
+ <DialogDescription />
233
+ </DialogHeader>
234
+
235
+ <h3 v-if="state.workingItem.userId === edgeFirebase.user.uid && adminCount > 1">
236
+ Are you sure you want to remove yourself from the organization "{{ currentOrganization.name }}"? You will no longer have access to any of the organization's data.
237
+ </h3>
238
+ <h3 v-else-if="state.workingItem.userId === edgeFirebase.user.uid && adminCount === 1">
239
+ You cannot remove yourself from this organization because you are the only admin. You can delete the organization or add another admin.
240
+ </h3>
241
+ <h3 v-else>
242
+ Are you sure you want to remove "{{ state.workingItem.meta.name }}" from the organization "{{ currentOrganization.name }}"?
243
+ </h3>
244
+ <DialogFooter class="pt-6 flex justify-between">
245
+ <edge-shad-button class="text-white bg-slate-800 hover:bg-slate-400" @click="state.deleteDialog = false">
246
+ Cancel
247
+ </edge-shad-button>
248
+ <edge-shad-button
249
+ :disabled="adminCount === 1 && state.workingItem.userId === edgeFirebase.user.uid"
250
+ class="w-full"
251
+ variant="destructive"
252
+ @click="deleteAction()"
253
+ >
254
+ <span v-if="state.workingItem.userId === edgeFirebase.user.uid">
255
+ Leave
256
+ </span>
257
+ <span v-else>
258
+ Remove
259
+ </span>
260
+ </edge-shad-button>
261
+ </DialogFooter>
262
+ </DialogContent>
263
+ </edge-shad-dialog>
264
+ <edge-shad-dialog
265
+ v-model="state.dialog"
266
+ >
267
+ <DialogContent>
268
+ <edge-shad-form :initial-values="state.workingItem" :schema="computedUserSchema" @submit="onSubmit">
269
+ <DialogHeader>
270
+ <DialogTitle>
271
+ {{ state.currentTitle }}
272
+ </DialogTitle>
273
+ <DialogDescription />
274
+ </DialogHeader>
275
+
276
+ <edge-g-input
277
+ v-model="state.workingItem.name"
278
+ name="name"
279
+ :disable-tracking="true"
280
+ field-type="text"
281
+ label="Name"
282
+ :parent-tracker-id="`inviteUser-${state.workingItem.id}`"
283
+ :disabled="state.saveButton !== 'Invite User'"
284
+ />
285
+ <edge-g-input
286
+ v-if="state.saveButton === 'Invite User'"
287
+ v-model="state.workingItem.email"
288
+ name="email"
289
+ :disable-tracking="true"
290
+ field-type="text"
291
+ label="Email"
292
+ :parent-tracker-id="`inviteUser-${state.workingItem.id}`"
293
+ />
294
+ <edge-g-input
295
+ v-if="state.saveButton === 'Invite User'"
296
+ v-model="state.workingItem.isTemplate"
297
+ name="isTemplate"
298
+ :disable-tracking="true"
299
+ field-type="boolean"
300
+ label="Template User"
301
+ :parent-tracker-id="`inviteUser-${state.workingItem.id}`"
302
+ />
303
+ <div class="mb-4" />
304
+ <edge-g-input
305
+ v-model="state.workingItem.role"
306
+ name="role"
307
+ :disable-tracking="true"
308
+ :items="roleNamesOnly"
309
+ field-type="select"
310
+ label="Role"
311
+ :parent-tracker-id="`inviteUser-${state.workingItem.id}`"
312
+ :disabled="state.workingItem.userId === edgeFirebase.user.uid"
313
+ />
314
+ <DialogFooter class="pt-6 flex justify-between">
315
+ <edge-shad-button variant="destructive" @click="closeDialog">
316
+ Cancel
317
+ </edge-shad-button>
318
+ <edge-shad-button
319
+ :disabled="state.loading"
320
+ class="text-white w-100 bg-slate-800 hover:bg-slate-400"
321
+ type="submit"
322
+ >
323
+ <Loader2 v-if="state.loading" class="w-4 h-4 mr-2 animate-spin" />
324
+ {{ state.saveButton }}
325
+ </edge-shad-button>
326
+ </DialogFooter>
327
+ </edge-shad-form>
328
+ </DialogContent>
329
+ </edge-shad-dialog>
330
+ </template>
331
+
332
+ <style lang="scss" scoped>
333
+ .pointer {
334
+ cursor: move;
335
+ }
336
+ </style>
@@ -0,0 +1,57 @@
1
+ <script setup>
2
+ const props = defineProps({
3
+ field: {
4
+ type: Object,
5
+ required: true,
6
+ },
7
+ })
8
+ // Your JavaScript code goes here
9
+ </script>
10
+
11
+ <template>
12
+ <div>
13
+ <edge-chip>
14
+ Type: {{ field.type }}
15
+ </edge-chip>
16
+ <edge-chip>
17
+ Required
18
+ </edge-chip>
19
+ <DropdownMenu
20
+ v-if="field?.enum?.length"
21
+ >
22
+ <DropdownMenuTrigger as-child>
23
+ <edge-chip class="ml-1 cursor-pointer">
24
+ Enum
25
+ </edge-chip>
26
+ </DropdownMenuTrigger>
27
+ <DropdownMenuContent>
28
+ <edge-v-alert class="w-[300px]">
29
+ <div class="text-lg">
30
+ Enum
31
+ </div>
32
+ {{ field.enum.join(', ') }}
33
+ </edge-v-alert>
34
+ </DropdownMenuContent>
35
+ </DropdownMenu>
36
+ <DropdownMenu
37
+ v-if="field.description"
38
+ >
39
+ <DropdownMenuTrigger as-child>
40
+ <edge-chip class="ml-1 cursor-pointer">
41
+ Description
42
+ </edge-chip>
43
+ </DropdownMenuTrigger>
44
+ <DropdownMenuContent>
45
+ <edge-v-alert class="w-[300px]">
46
+ <div class="text-lg">
47
+ Description
48
+ </div>
49
+ {{ field.description }}
50
+ </edge-v-alert>
51
+ </DropdownMenuContent>
52
+ </DropdownMenu>
53
+ </div>
54
+ </template>
55
+
56
+ <style scoped>
57
+ </style>
@@ -0,0 +1,98 @@
1
+ <script setup>
2
+ import { defineProps } from 'vue'
3
+ const props = defineProps({
4
+ error: {
5
+ type: String,
6
+ default: '',
7
+ },
8
+ })
9
+
10
+ const firebaseErrorMap = {
11
+ 'auth/invalid-email': 'Oops! That doesn\'t look like a valid email address.',
12
+ 'auth/user-disabled': 'Oh no! This user account is currently disabled.',
13
+ 'auth/user-not-found': 'Hmm, we couldn\'t find a user with that email.',
14
+ 'auth/wrong-password': 'Whoops! That password doesn\'t seem to be correct.',
15
+ 'auth/email-already-in-use': 'It looks like someone else is already using that email address!',
16
+ 'auth/weak-password': 'Your password should be stronger! Try adding more characters or symbols.',
17
+ 'auth/operation-not-allowed': 'Sorry, this operation is not allowed.',
18
+ 'auth/account-exists-with-different-credential': 'This account already exists, but with different credentials.',
19
+ 'auth/credential-already-in-use': 'These credentials are already being used by someone else.',
20
+ 'auth/user-token-expired': 'Your session has expired. Please log in again.',
21
+ 'auth/network-request-failed': 'Oops, we couldn\'t connect to the server. Check your internet connection.',
22
+ 'storage/object-not-found': 'Oh dear, we couldn\'t find the file you\'re looking for.',
23
+ 'storage/bucket-not-found': 'We couldn\'t find the storage bucket you\'re looking for.',
24
+ 'storage/project-not-found': 'Hmm, we couldn\'t find the project you\'re looking for.',
25
+ 'storage/quota-exceeded': 'You\'ve reached your storage limit! Please try again later.',
26
+ 'storage/unauthorized': 'Hold on! You don\'t have permission to access this resource.',
27
+ 'storage/canceled': 'The upload was canceled.',
28
+ 'storage/invalid-checksum': 'The file seems to be corrupted. Please try again.',
29
+ 'firestore/permission-denied': 'You don\'t have permission to access this data.',
30
+ 'firestore/unavailable': 'Our Firestore service is taking a break. Please try again later.',
31
+ 'firestore/internal': 'Something went wrong on our end. We\'ll look into it!',
32
+ 'firestore/invalid-argument': 'You provided an invalid argument. Please double-check!',
33
+ 'firestore/cancelled': 'Operation cancelled. No worries!',
34
+ 'auth/popup-closed-by-user': 'Oops! The authentication popup was closed before the process could complete. Please try again.',
35
+ 'auth/invalid-verification-code': 'Uh oh! That verification code doesn\'t seem to be correct. Please try again.',
36
+ 'auth/too-many-requests': 'Whoa! You\'ve made too many attempts in a short period of time. Please wait a bit before trying again.',
37
+ 'auth/invalid-login-credentials': 'The provided login credentials are invalid. Please try again.',
38
+ 'auth/admin-restricted-operation': 'This operation is restricted to administrators only.',
39
+ 'auth/argument-error': 'There seems to be an error with the provided argument. Please check and try again.',
40
+ 'auth/app-not-authorized': 'This app is not authorized. Please contact the administrator.',
41
+ 'auth/app-not-installed': 'The app is not installed. Please install the app and try again.',
42
+ 'auth/captcha-check-failed': 'The captcha check failed. Please try again.',
43
+ 'auth/code-expired': 'The code has expired. Please request a new one.',
44
+ 'auth/cordova-not-ready': 'The system is not ready yet. Please wait a moment and try again.',
45
+ 'auth/cors-unsupported': 'Your browser does not support CORS. Please update your browser or try a different one.',
46
+ 'auth/custom-token-mismatch': 'The provided custom token does not match. Please check and try again.',
47
+ 'auth/requires-recent-login': 'For security reasons, this operation requires recent authentication. Log in again before retrying this request.',
48
+ 'auth/dependent-sdk-initialized-before-auth': 'An internal error occurred: a dependent SDK was initialized before the auth module.',
49
+ 'auth/dynamic-link-not-activated': 'The dynamic link is not activated. Please contact support.',
50
+ 'auth/email-change-needs-verification': 'Your email change needs verification. Please check your email for a verification link.',
51
+ 'auth/emulator-config-failed': 'The emulator configuration failed. Please check your setup.',
52
+ 'auth/expired-action-code': 'The action code has expired. Please request a new one.',
53
+ 'auth/cancelled-popup-request': 'The popup request was cancelled. Please try again.',
54
+ 'auth/internal-error': 'An internal error occurred. Please try again later.',
55
+ // ... Add more error codes as needed
56
+ }
57
+
58
+ const getReadableFirebaseError = (errorCode) => {
59
+ let code = ''
60
+
61
+ if (typeof errorCode === 'string') {
62
+ const match = errorCode.match(/(?:Firebase: Error \()?([a-z/,-]+)\)?/)
63
+ code = match ? match[1] : ''
64
+ }
65
+ else if (errorCode instanceof Error) {
66
+ const match = errorCode.message.match(/(?:Firebase: Error \()?([a-z/,-]+)\)?/)
67
+ code = match ? match[1] : ''
68
+ }
69
+
70
+ if (!code) {
71
+ console.log('Invalid error code:', errorCode)
72
+ return 'An invalid error code was received. Please try again later.'
73
+ }
74
+
75
+ const readableError = firebaseErrorMap[code]
76
+
77
+ if (readableError) {
78
+ return readableError
79
+ }
80
+ else {
81
+ console.log('Unknown Firebase error:', errorCode)
82
+ return errorCode instanceof Error ? errorCode.message : errorCode
83
+ }
84
+ }
85
+ </script>
86
+
87
+ <template>
88
+ <Alert class="mt-2 bg-red-800">
89
+ <AlertTitle>Error</AlertTitle>
90
+ <AlertDescription>
91
+ {{ getReadableFirebaseError(props.error) }}
92
+ </AlertDescription>
93
+ </Alert>
94
+ </template>
95
+
96
+ <style scoped>
97
+
98
+ </style>
@@ -0,0 +1,67 @@
1
+ <!-- eslint-disable vue/no-v-text-v-html-on-component -->
2
+ <script setup>
3
+ import { computed, defineProps, reactive } from 'vue'
4
+ const props = defineProps({
5
+ helper: {
6
+ type: String,
7
+ default: null,
8
+ },
9
+ title: {
10
+ type: String,
11
+ default: null,
12
+ },
13
+ })
14
+
15
+ const state = reactive({
16
+ titles: [
17
+ 'Field Assistance',
18
+ 'Form Help',
19
+ 'Input Guide',
20
+ 'Need Assistance?',
21
+ 'Help Center',
22
+ 'Field Support',
23
+ 'Quick Help',
24
+ 'Form Assistance',
25
+ 'Guidance Available',
26
+ 'Information Station',
27
+ 'Here to Help',
28
+ 'Form Guidance',
29
+ 'Input Support',
30
+ 'Solutions Available',
31
+ 'Your Guide',
32
+ 'Support Hub',
33
+ 'Input Assistance',
34
+ 'Help Desk',
35
+ 'Support Center',
36
+ ],
37
+ })
38
+ const newTitle = computed(() => {
39
+ if (!props.title) {
40
+ return state.titles[Math.floor(Math.random() * state.titles.length)]
41
+ }
42
+ return `${props.title} Help`
43
+ })
44
+ </script>
45
+
46
+ <template>
47
+ <DropdownMenu>
48
+ <DropdownMenuTrigger as-child>
49
+ <Button variant="text" size="icon" class="p-0 w-5 h-5">
50
+ <Info class="w-5 h-5" />
51
+ </Button>
52
+ </DropdownMenuTrigger>
53
+ <DropdownMenuContent class="absolute right-0 w-screen max-w-[600px]">
54
+ <Card class="border-0">
55
+ <CardHeader class="py-2">
56
+ <CardTitle class="text-xl">
57
+ {{ newTitle }}
58
+ </CardTitle>
59
+ </CardHeader>
60
+ <CardContent v-html="props.helper" />
61
+ </Card>
62
+ </DropdownMenuContent>
63
+ </DropdownMenu>
64
+ </template>
65
+
66
+ <style lang="scss">
67
+ </style>