@7365admin1/layer-common 4.2.0 → 4.2.1-staging.259

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.
@@ -418,6 +418,42 @@
418
418
 
419
419
  <v-divider />
420
420
 
421
+ <!-- Invitation Note -->
422
+ <v-col cols="12">
423
+ <div class="mt-5 pb-5">
424
+ <div class="d-flex pr-5">
425
+ <p class="mr-6 font-weight-medium flex-grow-1">
426
+ Invitation Note
427
+ <span class="d-block text-caption screen-hint mt-1">
428
+ Turning this on appends the text message below as a note
429
+ within the contractor's invitation link.
430
+ </span>
431
+ </p>
432
+ <v-switch
433
+ color="success"
434
+ hide-details
435
+ v-model="currentEntryPassSettings.onboardingInvitationNoteEnabled"
436
+ @update:modelValue="updateInvitationNoteEnabled()"
437
+ />
438
+ </div>
439
+ <v-textarea
440
+ placeholder="Enter note here"
441
+ variant="outlined"
442
+ density="comfortable"
443
+ rows="4"
444
+ no-resize
445
+ class="mt-3"
446
+ :disabled="!currentEntryPassSettings.onboardingInvitationNoteEnabled"
447
+ :error-messages="invitationNoteError"
448
+ v-model="currentEntryPassSettings.onboardingInvitationNoteText"
449
+ @update:modelValue="invitationNoteError = ''"
450
+ @blur="saveInvitationNote()"
451
+ />
452
+ </div>
453
+ </v-col>
454
+
455
+ <v-divider />
456
+
421
457
  <!-- Printer Setup -->
422
458
  <v-col cols="12">
423
459
  <div class="mt-5 pb-5">
@@ -609,6 +645,8 @@ const currentEntryPassSettings = ref<Record<string, any>>({
609
645
  onboardingSubText: "",
610
646
  onboardingPrinterVendorId: "",
611
647
  onboardingPrinterProductId: "",
648
+ onboardingInvitationNoteEnabled: false,
649
+ onboardingInvitationNoteText: "",
612
650
  status: "",
613
651
  createdBy: "",
614
652
  updatedBy: "",
@@ -799,6 +837,10 @@ watchEffect(() => {
799
837
  onboardingPrinterProductId: data.settings?.onboarding?.printer?.productId
800
838
  ? String(data.settings.onboarding.printer.productId)
801
839
  : "",
840
+ onboardingInvitationNoteEnabled:
841
+ data.settings?.onboarding?.invitationNote?.isEnabled ?? false,
842
+ onboardingInvitationNoteText:
843
+ data.settings?.onboarding?.invitationNote?.text ?? "",
802
844
  createdAt: data.createdAt ?? "",
803
845
  updatedAt: data.updatedAt ?? "",
804
846
  };
@@ -844,6 +886,14 @@ function buildPayload() {
844
886
  vendorId: Number(currentEntryPassSettings.value.onboardingPrinterVendorId) || null,
845
887
  productId: Number(currentEntryPassSettings.value.onboardingPrinterProductId) || null,
846
888
  },
889
+ invitationNote: {
890
+ isEnabled:
891
+ currentEntryPassSettings.value.onboardingInvitationNoteEnabled ?? false,
892
+ // Kept even while the toggle is off - flipping it off is "stop
893
+ // appending this", not "throw away what I typed", so turning it
894
+ // back on restores the same note rather than a blank box.
895
+ text: (currentEntryPassSettings.value.onboardingInvitationNoteText || "").trim(),
896
+ },
847
897
  },
848
898
  },
849
899
  };
@@ -893,6 +943,43 @@ async function updateOnboardingEnabled() {
893
943
  getSiteEntryPassSettings();
894
944
  }
895
945
 
946
+ /** Blank while the toggle is on means every contractor registration would
947
+ get an empty note appended - flagged on the field rather than silently
948
+ saved, mirroring the resident-contractor settings screen. */
949
+ const invitationNoteError = ref("");
950
+
951
+ function invitationNoteIsValid() {
952
+ const enabled = currentEntryPassSettings.value.onboardingInvitationNoteEnabled;
953
+ const text = (currentEntryPassSettings.value.onboardingInvitationNoteText || "").trim();
954
+ if (enabled && !text) {
955
+ invitationNoteError.value = "Enter the note to append to the invitation link.";
956
+ return false;
957
+ }
958
+ invitationNoteError.value = "";
959
+ return true;
960
+ }
961
+
962
+ async function updateInvitationNoteEnabled() {
963
+ if (!invitationNoteIsValid()) return;
964
+ await _save(buildPayload());
965
+ showMessage("Invitation note updated!", "success");
966
+ getSiteEntryPassSettings();
967
+ }
968
+
969
+ // This section has no Save button of its own (the one below it belongs to
970
+ // Printer Setup), so the note text persists on blur - otherwise a site
971
+ // could type a note, flip nothing else, and lose it on navigation.
972
+ async function saveInvitationNote() {
973
+ if (!invitationNoteIsValid()) return;
974
+ const saved = getSiteEntryPassSettingsReq.value?.data?.settings?.onboarding
975
+ ?.invitationNote?.text ?? "";
976
+ const current = (currentEntryPassSettings.value.onboardingInvitationNoteText || "").trim();
977
+ if (saved === current) return;
978
+ await _save(buildPayload());
979
+ showMessage("Invitation note saved!", "success");
980
+ getSiteEntryPassSettings();
981
+ }
982
+
896
983
  async function scanOnboardingPrinter() {
897
984
  if (!isWebUsbSupported.value) {
898
985
  showMessage("Web USB is not supported in this browser", "error");
@@ -160,6 +160,15 @@ const props = defineProps({
160
160
  type: String,
161
161
  default: "",
162
162
  },
163
+ /*
164
+ * Pre-bind the invitation to one site.
165
+ *
166
+ * This prop has been declared since the component was written and was never
167
+ * read anywhere - `invite.site` was only ever filled by the Site picker. A
168
+ * caller that already knows the site (onboarding step 6, "Invite Site Admin",
169
+ * which is about the site created in step 4) had no way to say so. It is
170
+ * wired to `invite` below.
171
+ */
163
172
  site: {
164
173
  type: Object as PropType<TCustomerSite>,
165
174
  default: () => ({
@@ -225,17 +234,34 @@ watch(
225
234
  () => {
226
235
  invite.value.org = props.org || ""
227
236
 
228
- if (props.hideApp) {
229
- invite.value.app = "property_management_agency"
230
- return
231
- }
232
-
237
+ /*
238
+ * `hideApp` HIDES the App field (see the `v-if` in the template). It also
239
+ * used to overwrite the value with a literal "property_management_agency",
240
+ * so a caller that hid the field could not choose what it was hiding, and
241
+ * every organisation - security, cleaning, landscaping - was invited into
242
+ * property management. Hiding a field is not the same as deciding it.
243
+ *
244
+ * `adminState: false` keeps its existing fallback: that flag is about the
245
+ * ORGANISATION-admin invitation, which is a separate flow, and changing it
246
+ * here would move where those members land.
247
+ */
233
248
  invite.value.app = props.adminState
234
249
  ? (props.app || "")
235
250
  : "property_management_agency"
236
251
  },
237
252
  { immediate: true }
238
253
  )
254
+
255
+ /* A caller-supplied site wins; the picker still works when there is none. */
256
+ watch(
257
+ () => props.site,
258
+ (site) => {
259
+ if (!site?.site) return
260
+ invite.value.site = site.site
261
+ invite.value.siteName = site.name || ""
262
+ },
263
+ { immediate: true, deep: true }
264
+ )
239
265
  // invite.value.app = props.app;
240
266
  // invite.value.org = props.org ?? "";
241
267
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "4.2.0",
5
+ "version": "4.2.1-staging.259",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",