@iservice365/layer-common 0.0.6 → 0.2.0

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 (91) hide show
  1. package/.playground/app.vue +7 -2
  2. package/.playground/pages/feedback.vue +30 -0
  3. package/CHANGELOG.md +12 -0
  4. package/components/Chat/Bubbles.vue +53 -0
  5. package/components/Chat/Information.vue +187 -0
  6. package/components/Chat/ListCard.vue +62 -0
  7. package/components/Chat/Message.vue +149 -0
  8. package/components/Chat/Navigation.vue +150 -0
  9. package/components/ConfirmDialog.vue +66 -0
  10. package/components/Container/Standard.vue +33 -0
  11. package/components/Feedback/Form.vue +136 -0
  12. package/components/FeedbackDetail.vue +465 -0
  13. package/components/FeedbackMain.vue +454 -0
  14. package/components/FormDialog.vue +65 -0
  15. package/components/Input/File.vue +203 -0
  16. package/components/Input/ListGroupSelection.vue +96 -0
  17. package/components/Input/NewDate.vue +123 -0
  18. package/components/Input/Number.vue +124 -0
  19. package/components/InvitationMain.vue +284 -0
  20. package/components/Layout/Header.vue +28 -4
  21. package/components/ListView.vue +87 -0
  22. package/components/MemberMain.vue +459 -0
  23. package/components/RolePermissionFormCreate.vue +161 -0
  24. package/components/RolePermissionFormPreviewUpdate.vue +183 -0
  25. package/components/RolePermissionMain.vue +361 -0
  26. package/components/ServiceProviderFormCreate.vue +154 -0
  27. package/components/ServiceProviderMain.vue +195 -0
  28. package/components/SignaturePad.vue +73 -0
  29. package/components/SpecificAttr.vue +53 -0
  30. package/components/SwitchContext.vue +26 -5
  31. package/components/TableList.vue +150 -0
  32. package/components/TableListSecondary.vue +164 -0
  33. package/components/WorkOrder/Create.vue +197 -0
  34. package/components/WorkOrder/ListView.vue +96 -0
  35. package/components/WorkOrder/Main.vue +308 -0
  36. package/components/Workorder.vue +1 -0
  37. package/composables/useAddress.ts +107 -0
  38. package/composables/useCommonPermission.ts +130 -0
  39. package/composables/useCustomer.ts +113 -0
  40. package/composables/useFeedback.ts +117 -0
  41. package/composables/useFile.ts +40 -0
  42. package/composables/useInvoice.ts +18 -0
  43. package/composables/useLocal.ts +24 -4
  44. package/composables/useLocalAuth.ts +58 -14
  45. package/composables/useLocalSetup.ts +52 -0
  46. package/composables/useMember.ts +104 -0
  47. package/composables/useOrg.ts +76 -92
  48. package/composables/usePaymentMethod.ts +101 -0
  49. package/composables/usePrice.ts +15 -0
  50. package/composables/usePromoCode.ts +36 -0
  51. package/composables/useRole.ts +38 -7
  52. package/composables/useServiceProvider.ts +218 -0
  53. package/composables/useSite.ts +108 -0
  54. package/composables/useSubscription.ts +149 -0
  55. package/composables/useUser.ts +38 -14
  56. package/composables/useUtils.ts +218 -6
  57. package/composables/useVerification.ts +33 -0
  58. package/composables/useWorkOrder.ts +68 -0
  59. package/middleware/01.auth.ts +11 -0
  60. package/middleware/02.org.ts +18 -0
  61. package/middleware/03.customer.ts +13 -0
  62. package/nuxt.config.ts +2 -1
  63. package/package.json +7 -3
  64. package/pages/index.vue +3 -0
  65. package/pages/payment-method-linked.vue +31 -0
  66. package/pages/require-customer.vue +56 -0
  67. package/pages/require-organization-membership.vue +47 -0
  68. package/pages/unauthorized.vue +29 -0
  69. package/plugins/API.ts +1 -3
  70. package/plugins/iconify.client.ts +5 -0
  71. package/plugins/vuetify.ts +2 -0
  72. package/public/bg-camera.jpg +0 -0
  73. package/public/bg-city.jpg +0 -0
  74. package/public/bg-condo.jpg +0 -0
  75. package/public/images/icons/delete-icon.png +0 -0
  76. package/public/sprite.svg +1 -0
  77. package/types/address.d.ts +13 -0
  78. package/types/customer.d.ts +15 -0
  79. package/types/feedback.d.ts +63 -0
  80. package/types/local.d.ts +46 -38
  81. package/types/member.d.ts +21 -0
  82. package/types/org.d.ts +13 -0
  83. package/types/permission.d.ts +1 -0
  84. package/types/price.d.ts +17 -0
  85. package/types/promo-code.d.ts +19 -0
  86. package/types/service-provider.d.ts +15 -0
  87. package/types/site.d.ts +13 -0
  88. package/types/subscription.d.ts +23 -0
  89. package/types/user.d.ts +19 -0
  90. package/types/verification.d.ts +20 -0
  91. package/types/work-order.d.ts +40 -0
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <v-input v-bind="attrs">
3
+ <v-card width="100%" v-bind="attrs">
4
+ <v-list
5
+ v-model:selected="selected"
6
+ lines="two"
7
+ :select-strategy="attrs.readonly ? 'classic' : 'leaf'"
8
+ class="pa-0"
9
+ density="compact"
10
+ read-only
11
+ open-strategy="single"
12
+ >
13
+ <template
14
+ v-for="(permission, permissionKey, permissionIndex) in props.items"
15
+ :key="permissionKey"
16
+ >
17
+ <v-divider v-if="permissionIndex > 0"></v-divider>
18
+ <v-list-group :value="permissionKey" fluid>
19
+ <template v-slot:activator="{ props }">
20
+ <v-list-item v-bind="props" density="compact">
21
+ <span class="text-capitalize">
22
+ {{ String(permissionKey).replace(/-/g, " ") }}
23
+ </span>
24
+
25
+ <template #prepend>
26
+ <v-chip class="mr-2" small>
27
+ {{ selectedActionCount(String(permissionKey)) }}
28
+ </v-chip>
29
+ </template>
30
+ </v-list-item>
31
+ </template>
32
+
33
+ <template v-for="(item, itemKey) in permission" :key="itemKey">
34
+ <v-divider></v-divider>
35
+ <v-list-item v-if="attrs.readonly" density="compact">
36
+ <template #title class="pl-2">
37
+ <span class="text-subtitle-2 text-capitalize">
38
+ {{ String(itemKey).replace(/-/g, " ") }}
39
+ </span>
40
+ </template>
41
+
42
+ <template #subtitle class="pl-2">
43
+ <span class="text-subtitle-2">{{ item.description }}</span>
44
+ </template>
45
+ </v-list-item>
46
+
47
+ <v-list-item
48
+ v-else
49
+ :value="`${permissionKey}:${itemKey}`"
50
+ density="compact"
51
+ >
52
+ <template #title class="pl-2">
53
+ <span class="text-subtitle-2 text-capitalize">
54
+ {{ String(itemKey).replace(/-/g, " ") }}
55
+ </span>
56
+ </template>
57
+
58
+ <template #subtitle class="pl-2">
59
+ <span class="text-subtitle-2 text-capitalize">
60
+ {{ String(item.description).replace(/-/g, " ") }}
61
+ </span>
62
+ </template>
63
+
64
+ <template #prepend="{ isSelected, select }" class="pl-1">
65
+ <v-list-item-action start>
66
+ <v-checkbox-btn
67
+ :model-value="isSelected"
68
+ @update:model-value="select"
69
+ ></v-checkbox-btn>
70
+ </v-list-item-action>
71
+ </template>
72
+ </v-list-item>
73
+ </template>
74
+ </v-list-group>
75
+ </template>
76
+ </v-list>
77
+ </v-card>
78
+ </v-input>
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ const selected = defineModel<Array<string>>({ default: [] });
83
+ const attrs = useAttrs();
84
+ const props = defineProps({
85
+ items: {
86
+ type: Object,
87
+ required: true,
88
+ default: () => ({}),
89
+ },
90
+ });
91
+
92
+ const selectedActionCount = (resource: string) => {
93
+ return selected.value.filter((permission) => permission.startsWith(resource))
94
+ .length;
95
+ };
96
+ </script>
@@ -0,0 +1,123 @@
1
+ <script setup lang="ts">
2
+ import { defineModel, defineProps, computed, ref } from "vue";
3
+
4
+ const props = defineProps<{
5
+ rules?: ((value: string) => boolean | string)[]; // Accepts external validation rules
6
+ }>();
7
+
8
+ const dateValue = defineModel<string>({ default: "" });
9
+ const inputRef = ref<HTMLInputElement | null>(null);
10
+
11
+ const formatDate = (event: Event) => {
12
+ const input = event.target as HTMLInputElement;
13
+ let value = input.value.replace(/\D/g, ""); // Remove non-numeric characters
14
+
15
+ // Format as MM/DD/YYYY
16
+ let formattedValue = value
17
+ .slice(0, 8)
18
+ .replace(/(\d{2})(\d{0,2})?(\d{0,4})?/, (_, m, d, y) =>
19
+ [m, d, y].filter(Boolean).join("/")
20
+ );
21
+
22
+ // Preserve cursor position
23
+ const cursorPosition = input.selectionStart ?? 0;
24
+ const slashCountBefore = (dateValue.value.match(/\//g) || []).length;
25
+ const slashCountAfter = (formattedValue.match(/\//g) || []).length;
26
+ const cursorOffset = slashCountAfter - slashCountBefore;
27
+
28
+ // Only update if value changed to prevent unnecessary reactivity updates
29
+ if (dateValue.value !== formattedValue) {
30
+ dateValue.value = formattedValue;
31
+ setTimeout(() => {
32
+ input.setSelectionRange(
33
+ cursorPosition + cursorOffset,
34
+ cursorPosition + cursorOffset
35
+ );
36
+ });
37
+ }
38
+ };
39
+
40
+ // Compute combined validation rules
41
+ const computedRules = computed(() => {
42
+ return props.rules ? [...props.rules] : [];
43
+ });
44
+
45
+ // Handle arrow key increments with cursor preservation
46
+ const handleArrowKeys = (event: KeyboardEvent) => {
47
+ if (!dateValue.value) return;
48
+
49
+ const input = event.target as HTMLInputElement;
50
+ const cursorPosition = input.selectionStart ?? 0; // Store cursor position
51
+ dateValue.value.split("/").map(Number);
52
+
53
+ let updatedDate = dateValue.value;
54
+
55
+ // Determine which part to modify
56
+ if (cursorPosition <= 2) {
57
+ updatedDate = modifyDatePart(
58
+ dateValue.value,
59
+ "month",
60
+ event.key === "ArrowUp" ? 1 : -1
61
+ );
62
+ } else if (cursorPosition <= 5) {
63
+ updatedDate = modifyDatePart(
64
+ dateValue.value,
65
+ "day",
66
+ event.key === "ArrowUp" ? 1 : -1
67
+ );
68
+ } else {
69
+ updatedDate = modifyDatePart(
70
+ dateValue.value,
71
+ "year",
72
+ event.key === "ArrowUp" ? 1 : -1
73
+ );
74
+ }
75
+
76
+ if (dateValue.value !== updatedDate) {
77
+ dateValue.value = updatedDate;
78
+ setTimeout(() => {
79
+ input.setSelectionRange(cursorPosition, cursorPosition);
80
+ });
81
+ }
82
+
83
+ event.preventDefault();
84
+ };
85
+
86
+ const modifyDatePart = (
87
+ date: string,
88
+ part: "month" | "day" | "year",
89
+ change: number
90
+ ) => {
91
+ let [month, day, year] = date.split("/").map(Number);
92
+
93
+ if (part === "month") {
94
+ month = Math.max(1, Math.min(12, month + change));
95
+ const maxDays = new Date(year, month, 0).getDate();
96
+ day = Math.min(day, maxDays); // Adjust day to fit new month's max days
97
+ } else if (part === "day") {
98
+ const maxDays = new Date(year, month, 0).getDate();
99
+ day = Math.max(1, Math.min(maxDays, day + change));
100
+ } else if (part === "year") {
101
+ year += change;
102
+ const maxDays = new Date(year, month, 0).getDate();
103
+ day = Math.min(day, maxDays); // Adjust day to fit new year's month
104
+ }
105
+
106
+ return `${String(month).padStart(2, "0")}/${String(day).padStart(
107
+ 2,
108
+ "0"
109
+ )}/${year}`;
110
+ };
111
+ </script>
112
+
113
+ <template>
114
+ <v-text-field
115
+ ref="inputRef"
116
+ v-model="dateValue"
117
+ placeholder="MM/DD/YYYY"
118
+ @input="formatDate"
119
+ @keydown.up="handleArrowKeys"
120
+ @keydown.down="handleArrowKeys"
121
+ :rules="computedRules"
122
+ ></v-text-field>
123
+ </template>
@@ -0,0 +1,124 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, nextTick, useAttrs } from "vue";
3
+
4
+ const props = defineProps({
5
+ infinityEnabled: { type: Boolean, default: false }, // Enable ∞ display for 0
6
+ });
7
+
8
+ const modelValue = defineModel<number>(); // Store actual number
9
+ const inputRef = ref<HTMLInputElement | null>(null);
10
+ const attrs = useAttrs();
11
+
12
+ let cursorPosition = 0;
13
+ let forceCursorToEnd = false;
14
+
15
+ // Computed property to format value with commas or infinity sign
16
+ const formattedValue = computed({
17
+ get: () => {
18
+ if (props.infinityEnabled && modelValue.value === 0) return "∞"; // Show ∞ if enabled
19
+ return modelValue.value?.toLocaleString() ?? "";
20
+ },
21
+ set: (val: string) => {
22
+ if (props.infinityEnabled && val === "∞") {
23
+ modelValue.value = 0; // Convert back to 0 internally
24
+ return;
25
+ }
26
+
27
+ const rawValue = val.replace(/\D/g, ""); // Remove non-numeric characters
28
+ const numericValue = rawValue ? Number(rawValue) : 0;
29
+
30
+ if (!isNaN(numericValue)) {
31
+ modelValue.value = numericValue;
32
+ nextTick(() => restoreCursor());
33
+ }
34
+ },
35
+ });
36
+
37
+ // Handle keydown for navigation & number changes
38
+ const handleKeyDown = (event: KeyboardEvent) => {
39
+ if (!inputRef.value || modelValue.value === undefined) return;
40
+
41
+ const { selectionStart, selectionEnd, value } = inputRef.value;
42
+ const isAllSelected = selectionStart === 0 && selectionEnd === value.length;
43
+
44
+ if (
45
+ !/^\d$/.test(event.key) &&
46
+ ![
47
+ "Backspace",
48
+ "Delete",
49
+ "ArrowLeft",
50
+ "ArrowRight",
51
+ "ArrowUp",
52
+ "ArrowDown",
53
+ ].includes(event.key)
54
+ ) {
55
+ event.preventDefault();
56
+ return;
57
+ }
58
+
59
+ if (isAllSelected && /^\d$/.test(event.key)) {
60
+ event.preventDefault();
61
+ modelValue.value = Number(event.key);
62
+ forceCursorToEnd = true;
63
+ nextTick(() => restoreCursor());
64
+ return;
65
+ }
66
+
67
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
68
+ forceCursorToEnd = false;
69
+ return;
70
+ }
71
+
72
+ if (event.key === "ArrowUp") {
73
+ event.preventDefault();
74
+ modelValue.value += 1;
75
+ forceCursorToEnd = true;
76
+ } else if (event.key === "ArrowDown") {
77
+ event.preventDefault();
78
+ modelValue.value = Math.max(0, modelValue.value - 1);
79
+ forceCursorToEnd = true;
80
+ } else {
81
+ cursorPosition = selectionStart || 0;
82
+ }
83
+
84
+ nextTick(() => restoreCursor());
85
+ };
86
+
87
+ // Restore cursor position
88
+ const restoreCursor = () => {
89
+ if (!inputRef.value) return;
90
+ const length = formattedValue.value.length;
91
+
92
+ if (forceCursorToEnd) {
93
+ inputRef.value.setSelectionRange(length, length);
94
+ } else {
95
+ inputRef.value.setSelectionRange(cursorPosition, cursorPosition);
96
+ }
97
+ };
98
+ </script>
99
+
100
+ <template>
101
+ <v-text-field
102
+ ref="inputRef"
103
+ v-model="formattedValue"
104
+ v-bind="attrs"
105
+ type="text"
106
+ @keydown="handleKeyDown"
107
+ >
108
+ <template v-if="$slots.prepend" v-slot:prepend>
109
+ <slot name="prepend"></slot>
110
+ </template>
111
+
112
+ <template v-if="$slots.append" v-slot:append>
113
+ <slot name="append"></slot>
114
+ </template>
115
+
116
+ <template v-if="$slots['append-inner']" v-slot:append-inner>
117
+ <slot name="append-inner"></slot>
118
+ </template>
119
+
120
+ <template v-if="$slots['prepend-inner']" v-slot:prepend-inner>
121
+ <slot name="prepend-inner"></slot>
122
+ </template>
123
+ </v-text-field>
124
+ </template>
@@ -0,0 +1,284 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-col cols="12" class="mb-2">
4
+ <v-row no-gutters>
5
+ <v-btn
6
+ class="text-none mr-2"
7
+ rounded="pill"
8
+ variant="tonal"
9
+ :to="{
10
+ name: 'org-customer-site-invitations-invite',
11
+ }"
12
+ size="large"
13
+ v-if="props.inviteMember"
14
+ >
15
+ Invite member
16
+ </v-btn>
17
+ </v-row>
18
+ </v-col>
19
+ <v-col cols="12">
20
+ <v-card width="100%" variant="outlined" border="thin" rounded="lg">
21
+ <v-toolbar density="compact" color="grey-lighten-4">
22
+ <template #prepend>
23
+ <v-btn
24
+ fab
25
+ icon
26
+ density="comfortable"
27
+ @click="_getVerifications({ search: headerSearch, status })"
28
+ >
29
+ <v-icon>mdi-refresh</v-icon>
30
+ </v-btn>
31
+ </template>
32
+
33
+ <template #append>
34
+ <v-row no-gutters justify="end" align="center">
35
+ <span class="mr-2 text-caption text-fontgray">
36
+ {{ pageRange }}
37
+ </span>
38
+ <local-pagination
39
+ v-model="page"
40
+ :length="pages"
41
+ @update:value="_getVerifications({ search: headerSearch })"
42
+ />
43
+ </v-row>
44
+ </template>
45
+
46
+ <template #extension>
47
+ <v-tabs>
48
+ <v-tab
49
+ :to="{
50
+ name: 'org-customer-site-invitations-status-status',
51
+ params: { status: 'pending' },
52
+ }"
53
+ >
54
+ Pending
55
+ </v-tab>
56
+ <v-tab
57
+ :to="{
58
+ name: 'org-customer-site-invitations-status-status',
59
+ params: { status: 'expired' },
60
+ }"
61
+ >
62
+ Expired
63
+ </v-tab>
64
+ </v-tabs>
65
+ </template>
66
+ </v-toolbar>
67
+
68
+ <v-data-table
69
+ :headers="headers"
70
+ :items="items"
71
+ item-value="_id"
72
+ items-per-page="20"
73
+ fixed-header
74
+ hide-default-footer
75
+ style="max-height: calc(100vh - (126px))"
76
+ :loading="loading"
77
+ >
78
+ <template #item.permissions="{ value }">
79
+ <span class="text-caption font-weight-bold text-capitalize">
80
+ permissions
81
+ </span>
82
+ <v-chip>{{ value.length }}</v-chip>
83
+ </template>
84
+ <template #item.createdAt="{ item }">
85
+ {{ formatDate(item.createdAt) }}
86
+ </template>
87
+ <template #item.action-table="{ item }">
88
+ <v-menu
89
+ :close-on-content-click="false"
90
+ offset-y
91
+ width="150"
92
+ v-if="props.cancelInvitation"
93
+ >
94
+ <template v-slot:activator="{ props }">
95
+ <v-icon v-bind="props">mdi-dots-horizontal</v-icon>
96
+ </template>
97
+ <v-list>
98
+ <v-list-item @click="openConfirmDialog(item._id)">
99
+ Cancel Invite
100
+ </v-list-item>
101
+ </v-list>
102
+ </v-menu>
103
+ </template>
104
+ </v-data-table>
105
+ </v-card>
106
+ </v-col>
107
+ </v-row>
108
+ <ConfirmDialog
109
+ v-model="confirmDialog"
110
+ :loading="cancelLoading"
111
+ @submit="onConfirmCancel"
112
+ >
113
+ <template #title>
114
+ <span class="font-weight-medium text-h5">Cancel Invitation</span>
115
+ </template>
116
+ <template #description>
117
+ <p class="text-subtitle-2">
118
+ Are you sure you want to cancel this invitation? This action cannot be
119
+ undone.
120
+ </p>
121
+ </template>
122
+ <template #footer>
123
+ <v-btn
124
+ variant="text"
125
+ @click="confirmDialog = false"
126
+ :disabled="cancelLoading"
127
+ >
128
+ Close
129
+ </v-btn>
130
+ <v-btn
131
+ color="primary"
132
+ variant="flat"
133
+ @click="onConfirmCancel"
134
+ :loading="cancelLoading"
135
+ >
136
+ Cancel Invite
137
+ </v-btn>
138
+ </template>
139
+ </ConfirmDialog>
140
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
141
+ </template>
142
+
143
+ <script setup lang="ts">
144
+ const props = defineProps({
145
+ route: {
146
+ type: String,
147
+ default: "index",
148
+ },
149
+ orgId: {
150
+ type: String,
151
+ default: "",
152
+ },
153
+ customerId: {
154
+ type: String,
155
+ default: "",
156
+ },
157
+ siteId: {
158
+ type: String,
159
+ default: "",
160
+ },
161
+ status: {
162
+ type: String,
163
+ default: "pending",
164
+ },
165
+ cancelInvitation: {
166
+ type: Boolean,
167
+ default: true,
168
+ },
169
+ inviteMember: {
170
+ type: Boolean,
171
+ default: true,
172
+ },
173
+ viewInvitations: {
174
+ type: Boolean,
175
+ default: true,
176
+ },
177
+ });
178
+
179
+ const { authenticate } = useLocalAuth();
180
+ authenticate();
181
+
182
+ const organization = (useRoute().params.org as string) ?? "";
183
+
184
+ const headers = [
185
+ {
186
+ title: "Date",
187
+ value: "createdAt",
188
+ },
189
+ {
190
+ title: "E-mail",
191
+ value: "email",
192
+ },
193
+ {
194
+ title: "Action",
195
+ value: "action-table",
196
+ },
197
+ ];
198
+
199
+ const status = (useRoute().params.status as string) ?? "";
200
+
201
+ const { getVerifications: _getVerifications, cancelUserInvitation } =
202
+ useVerification();
203
+
204
+ const page = ref(1);
205
+ const pages = ref(0);
206
+ const pageRange = ref("-- - -- of --");
207
+
208
+ const message = ref("");
209
+ const messageSnackbar = ref(false);
210
+ const messageColor = ref("");
211
+
212
+ const items = ref<Array<TMiniVerification>>([]);
213
+ const { headerSearch } = useLocal();
214
+ const { formatDate } = useUtils();
215
+
216
+ function showMessage(msg: string, color: string) {
217
+ message.value = msg;
218
+ messageColor.value = color;
219
+ messageSnackbar.value = true;
220
+ }
221
+
222
+ const {
223
+ data: getVerificationsReq,
224
+ refresh: getVerifications,
225
+ status: getVerificationsStatus,
226
+ } = useLazyAsyncData<{
227
+ items: TMiniVerification[];
228
+ pages: number;
229
+ pageRange: string;
230
+ }>("get-verifications" + props.status, () =>
231
+ _getVerifications({
232
+ page: page.value,
233
+ status: props.status,
234
+ search: headerSearch.value,
235
+ type: "user-invite",
236
+ })
237
+ );
238
+
239
+ const loading = computed(() => getVerificationsStatus.value === "pending");
240
+
241
+ watchEffect(() => {
242
+ if (getVerificationsReq.value) {
243
+ items.value = getVerificationsReq.value.items;
244
+ pages.value = getVerificationsReq.value.pages;
245
+ pageRange.value = getVerificationsReq.value.pageRange;
246
+ }
247
+ });
248
+
249
+ watch([page, headerSearch], () => {
250
+ getVerifications();
251
+ });
252
+
253
+ const confirmDialog = ref(false);
254
+ const selectedInviteId = ref<string | null>(null);
255
+ const cancelLoading = ref(false);
256
+
257
+ function openConfirmDialog(id: string) {
258
+ selectedInviteId.value = id;
259
+ confirmDialog.value = true;
260
+ }
261
+
262
+ async function onConfirmCancel() {
263
+ if (!selectedInviteId.value) return;
264
+
265
+ cancelLoading.value = true;
266
+ try {
267
+ const res = await cancelUserInvitation(selectedInviteId.value);
268
+
269
+ showMessage(res.message || "Invitation cancelled successfully", "success");
270
+ await getVerifications();
271
+ confirmDialog.value = false;
272
+ selectedInviteId.value = null;
273
+ } catch (error: any) {
274
+ showMessage(error?.message || "Failed to cancel invitation", "error");
275
+ }
276
+ cancelLoading.value = false;
277
+ }
278
+
279
+ watchEffect(() => {
280
+ if (!props.viewInvitations) {
281
+ useRouter().back();
282
+ }
283
+ });
284
+ </script>
@@ -1,6 +1,9 @@
1
1
  <template>
2
2
  <v-app-bar scroll-behavior="elevate" scroll-threshold="200">
3
- <v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
3
+ <v-app-bar-nav-icon
4
+ v-if="!props.hideNavIcon"
5
+ @click="drawer = !drawer"
6
+ ></v-app-bar-nav-icon>
4
7
 
5
8
  <template #append>
6
9
  <v-btn
@@ -55,6 +58,20 @@
55
58
  {{ currentUser?.firstName }} {{ currentUser?.lastName }}
56
59
  </v-col>
57
60
 
61
+ <v-col cols="12" class="mb-3">
62
+ <v-btn
63
+ v-if="APP_NAME.toLowerCase() !== 'account'"
64
+ block
65
+ rounded="xl"
66
+ variant="tonal"
67
+ size="x-large"
68
+ class="text-none text-subtitle-1 font-weight-regular"
69
+ @click="redirect(APP_ACCOUNT, 'home')"
70
+ >
71
+ Manage Account
72
+ </v-btn>
73
+ </v-col>
74
+
58
75
  <v-col cols="12">
59
76
  <v-btn
60
77
  block
@@ -77,6 +94,13 @@
77
94
  <script setup lang="ts">
78
95
  import { useTheme } from "vuetify";
79
96
 
97
+ const props = defineProps({
98
+ hideNavIcon: {
99
+ type: Boolean,
100
+ default: false,
101
+ },
102
+ });
103
+
80
104
  const menu = defineModel("menu", { type: Boolean });
81
105
 
82
106
  const search = defineModel("search", { type: String });
@@ -85,7 +109,7 @@ const { drawer } = useLocal();
85
109
 
86
110
  const { redirect } = useUtils();
87
111
 
88
- const { APP_ACCOUNT, APP_NAME } = useRuntimeConfig().public;
112
+ const { APP_ACCOUNT, APP_MAIN, APP_NAME } = useRuntimeConfig().public;
89
113
 
90
114
  const theme = useTheme();
91
115
 
@@ -100,8 +124,8 @@ const profile = computed(() => {
100
124
  });
101
125
 
102
126
  function logout() {
103
- if (APP_NAME.toLowerCase() !== "account") {
104
- redirect(`${APP_ACCOUNT}/logout`);
127
+ if (APP_NAME.toLowerCase() !== "main") {
128
+ redirect(APP_MAIN, "logout");
105
129
  return;
106
130
  }
107
131