@iservice365/layer-common 0.1.0 → 0.2.1

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 (93) 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 +14 -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 +62 -20
  45. package/composables/useLocalSetup.ts +13 -0
  46. package/composables/useMember.ts +111 -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/middleware/member.ts +4 -0
  63. package/nuxt.config.ts +3 -1
  64. package/package.json +7 -3
  65. package/pages/index.vue +3 -0
  66. package/pages/payment-method-linked.vue +31 -0
  67. package/pages/require-customer.vue +56 -0
  68. package/pages/require-organization-membership.vue +47 -0
  69. package/pages/unauthorized.vue +29 -0
  70. package/plugins/API.ts +2 -25
  71. package/plugins/iconify.client.ts +5 -0
  72. package/plugins/secure-member.client.ts +54 -0
  73. package/plugins/vuetify.ts +2 -0
  74. package/public/bg-camera.jpg +0 -0
  75. package/public/bg-city.jpg +0 -0
  76. package/public/bg-condo.jpg +0 -0
  77. package/public/images/icons/delete-icon.png +0 -0
  78. package/public/sprite.svg +1 -0
  79. package/types/address.d.ts +13 -0
  80. package/types/customer.d.ts +15 -0
  81. package/types/feedback.d.ts +63 -0
  82. package/types/local.d.ts +47 -38
  83. package/types/member.d.ts +21 -0
  84. package/types/org.d.ts +13 -0
  85. package/types/permission.d.ts +1 -0
  86. package/types/price.d.ts +17 -0
  87. package/types/promo-code.d.ts +19 -0
  88. package/types/service-provider.d.ts +15 -0
  89. package/types/site.d.ts +13 -0
  90. package/types/subscription.d.ts +23 -0
  91. package/types/user.d.ts +19 -0
  92. package/types/verification.d.ts +20 -0
  93. 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
@@ -91,6 +94,13 @@
91
94
  <script setup lang="ts">
92
95
  import { useTheme } from "vuetify";
93
96
 
97
+ const props = defineProps({
98
+ hideNavIcon: {
99
+ type: Boolean,
100
+ default: false,
101
+ },
102
+ });
103
+
94
104
  const menu = defineModel("menu", { type: Boolean });
95
105
 
96
106
  const search = defineModel("search", { type: String });
@@ -99,7 +109,7 @@ const { drawer } = useLocal();
99
109
 
100
110
  const { redirect } = useUtils();
101
111
 
102
- const { APP_ACCOUNT, APP_NAME } = useRuntimeConfig().public;
112
+ const { APP_ACCOUNT, APP_MAIN, APP_NAME } = useRuntimeConfig().public;
103
113
 
104
114
  const theme = useTheme();
105
115
 
@@ -114,8 +124,8 @@ const profile = computed(() => {
114
124
  });
115
125
 
116
126
  function logout() {
117
- if (APP_NAME.toLowerCase() !== "account") {
118
- redirect(`${APP_ACCOUNT}/logout`);
127
+ if (APP_NAME.toLowerCase() !== "main") {
128
+ redirect(APP_MAIN, "logout");
119
129
  return;
120
130
  }
121
131