@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201

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 (283) hide show
  1. package/assets/css/primitives.css +1113 -0
  2. package/assets/css/screens.css +1097 -0
  3. package/assets/css/shell.css +258 -0
  4. package/assets/css/tokens.css +236 -0
  5. package/components/AcceptDialog.vue +17 -18
  6. package/components/AccessCard/AvailableStats.vue +24 -16
  7. package/components/AccessCardAddForm.vue +42 -42
  8. package/components/AccessCardAssignToUnitForm.vue +13 -21
  9. package/components/AccessCardDeleteDialog.vue +41 -39
  10. package/components/AccessCardDetailsDialog.vue +125 -79
  11. package/components/AccessCardPreviewDialog.vue +58 -84
  12. package/components/AccessCardQrTagging.vue +60 -63
  13. package/components/AccessCardReplaceForm.vue +27 -23
  14. package/components/AccessManagement.vue +86 -85
  15. package/components/AddEqupmentForm.vue +18 -22
  16. package/components/AddPassKeyToVisitor.vue +95 -73
  17. package/components/AppButton.vue +62 -0
  18. package/components/AppCard.vue +20 -0
  19. package/components/AppDateField.vue +49 -0
  20. package/components/AppField.vue +76 -0
  21. package/components/AppKpiTile.vue +65 -0
  22. package/components/AppSegmented.vue +38 -0
  23. package/components/AppSelect.vue +247 -0
  24. package/components/AreaChecklistHistoryLogs.vue +51 -44
  25. package/components/AreaChecklistHistoryMain.vue +14 -7
  26. package/components/AreaFormDialog.vue +71 -59
  27. package/components/AreaMain.vue +73 -107
  28. package/components/AttachmentsViewer.vue +84 -22
  29. package/components/AttendanceCheckInOutDialog.vue +85 -88
  30. package/components/AttendanceDetailsDialog.vue +57 -63
  31. package/components/AttendanceMain.vue +4 -3
  32. package/components/AttendanceMapSearchDialog.vue +42 -44
  33. package/components/AttendanceSettingsDialog.vue +79 -63
  34. package/components/Avatar/Main.vue +54 -2
  35. package/components/BillingMain.vue +26 -30
  36. package/components/BtnUploadFile.vue +53 -11
  37. package/components/BuildingForm.vue +34 -27
  38. package/components/BuildingManagement/buildings.vue +39 -39
  39. package/components/BuildingManagement/units.vue +26 -42
  40. package/components/BuildingUnitFormAdd.vue +20 -23
  41. package/components/BuildingUnitFormEdit.vue +8 -4
  42. package/components/BulletinBoardForm.vue +19 -24
  43. package/components/BulletinBoardManagement.vue +49 -57
  44. package/components/BulletinBoardView.vue +9 -6
  45. package/components/BulletinExpirationChip.vue +13 -4
  46. package/components/Button/Close.vue +2 -2
  47. package/components/CameraForm.vue +21 -22
  48. package/components/CameraMain.vue +50 -49
  49. package/components/Card/DeleteConfirmation.vue +24 -22
  50. package/components/Card/MemberInfoSummary.vue +28 -3
  51. package/components/Card/Toggle.vue +15 -3
  52. package/components/CardToolbar.vue +37 -0
  53. package/components/Carousel.vue +56 -36
  54. package/components/Chat/Bubbles.vue +43 -14
  55. package/components/Chat/Information.vue +49 -32
  56. package/components/Chat/ListCard.vue +38 -9
  57. package/components/Chat/Message.vue +41 -15
  58. package/components/Chat/Navigation.vue +27 -15
  59. package/components/Chat/SkeletonLoader.vue +9 -2
  60. package/components/ChecklistItemRow.vue +25 -1
  61. package/components/CleaningScheduleMain.vue +44 -49
  62. package/components/ClientDetailForm.vue +81 -56
  63. package/components/ClientMain.vue +92 -82
  64. package/components/ConfirmDialog.vue +14 -8
  65. package/components/Container/Standard.vue +9 -7
  66. package/components/CreateSubsciptionPlan.vue +148 -89
  67. package/components/DashboardEmptyState.vue +48 -3
  68. package/components/DashboardMain.vue +924 -676
  69. package/components/DashboardPanel.vue +84 -21
  70. package/components/DeliveryCompany.vue +34 -20
  71. package/components/Dialog/DeleteConfirmation.vue +20 -21
  72. package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
  73. package/components/Dialog/ReusablePrompt.vue +30 -29
  74. package/components/Dialog/UpdateMoreAction.vue +62 -59
  75. package/components/DocumentForm.vue +14 -21
  76. package/components/DocumentManagement.vue +162 -108
  77. package/components/DocumentViewer.vue +29 -12
  78. package/components/DrawImage.vue +29 -21
  79. package/components/EntryPass/QrTemplatePreview.vue +27 -16
  80. package/components/EntryPassInformation.vue +40 -10
  81. package/components/EntryPassMain.vue +49 -27
  82. package/components/EquipmentItemMain.vue +84 -97
  83. package/components/EquipmentManagementMain.vue +81 -103
  84. package/components/Facility/BookingSetup.vue +43 -52
  85. package/components/Feedback/Form.vue +26 -12
  86. package/components/FeedbackMain.vue +35 -49
  87. package/components/FileInput.vue +75 -58
  88. package/components/FileInputWithList.vue +59 -12
  89. package/components/FormDialog.vue +60 -21
  90. package/components/HidAccessLogDashboard.vue +512 -490
  91. package/components/HidCameraCaptureDialog.vue +216 -0
  92. package/components/HidEnabledGate.vue +67 -9
  93. package/components/HidIntercomManagement.vue +1322 -653
  94. package/components/HidQrCodeConfiguration.vue +1030 -258
  95. package/components/HidReaderForm.vue +142 -59
  96. package/components/HidReaderManagement.vue +211 -323
  97. package/components/HidServiceSettingsPanel.vue +44 -13
  98. package/components/HidUserEnrollment.vue +1185 -412
  99. package/components/HygieneUpdateMoreAction.vue +44 -44
  100. package/components/IncidentReport/Authorities.vue +30 -18
  101. package/components/IncidentReport/IncidentInformation.vue +10 -10
  102. package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
  103. package/components/IncidentReport/affectedEntities.vue +41 -20
  104. package/components/Input/Date.vue +3 -0
  105. package/components/Input/DatePicker.vue +1 -2
  106. package/components/Input/DateTimePicker.vue +1 -1
  107. package/components/Input/File.vue +55 -10
  108. package/components/Input/FileV2.vue +39 -6
  109. package/components/Input/InputPhoneNumberV2.vue +2 -1
  110. package/components/Input/ListGroupSelection.vue +56 -8
  111. package/components/Input/NRICNumber.vue +1 -0
  112. package/components/Input/NewDate.vue +1 -0
  113. package/components/Input/Number.vue +1 -0
  114. package/components/Input/Password.vue +1 -0
  115. package/components/Input/PhoneNumber.vue +21 -1
  116. package/components/Input/VehicleNumber.vue +1 -0
  117. package/components/InputLabel.vue +2 -2
  118. package/components/InvitationClientForm.vue +21 -19
  119. package/components/InvitationForm.vue +15 -21
  120. package/components/InvitationMain.vue +46 -61
  121. package/components/Layout/Header.vue +263 -36
  122. package/components/Layout/NavigationDrawer.vue +300 -30
  123. package/components/ListItem.vue +33 -3
  124. package/components/LocalPagination.vue +27 -3
  125. package/components/ManageChecklistMain.vue +136 -86
  126. package/components/MemberInformation.vue +32 -16
  127. package/components/MemberMain.vue +113 -93
  128. package/components/MyAttendanceMain.vue +3 -5
  129. package/components/NavigationItem.vue +83 -3
  130. package/components/Nfc/NFCPatrolReportMain.vue +47 -33
  131. package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
  132. package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
  133. package/components/Nfc/NFCPatrolSettings.vue +14 -4
  134. package/components/Nfc/NFCTagForm.vue +22 -8
  135. package/components/Nfc/NFCTagMain.vue +41 -43
  136. package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
  137. package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
  138. package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
  139. package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
  140. package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
  141. package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
  142. package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
  143. package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
  144. package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
  145. package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
  146. package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
  147. package/components/NotificationSettings.vue +373 -0
  148. package/components/NumberSettingField.vue +9 -2
  149. package/components/OnlineFormConfigurationForm.vue +725 -237
  150. package/components/OnlineFormFill.vue +213 -106
  151. package/components/OnlineFormRenderPreview.vue +25 -30
  152. package/components/OnlineFormsConfiguration.vue +113 -109
  153. package/components/OrgViewDialog.vue +34 -16
  154. package/components/OvernightParkingAvailability.vue +49 -26
  155. package/components/OvernightParkingManagement.vue +78 -18
  156. package/components/PageHeader.vue +43 -0
  157. package/components/PassInformation.vue +109 -53
  158. package/components/PasswordConfirmation.vue +44 -26
  159. package/components/PeopleForm.vue +41 -20
  160. package/components/PhotoUpload.vue +110 -48
  161. package/components/PlaceholderComponent.vue +35 -19
  162. package/components/PlanActionsMenu.vue +12 -2
  163. package/components/PlanStatusBadge.vue +7 -8
  164. package/components/PlateNumberDisplay.vue +9 -6
  165. package/components/QrTemplate/PrintDialog.vue +66 -43
  166. package/components/RolePermissionFormCreate.vue +104 -79
  167. package/components/RolePermissionFormPreviewUpdate.vue +111 -109
  168. package/components/RolePermissionMain.vue +183 -81
  169. package/components/ScanVisitorQRCode.vue +99 -35
  170. package/components/ScheduleAreaMain.vue +34 -59
  171. package/components/ScheduleTaskForm.vue +61 -42
  172. package/components/ScheduleTaskMain.vue +56 -69
  173. package/components/SearchVehicleNumberUser.vue +39 -37
  174. package/components/ServiceProviderFormCreate.vue +29 -20
  175. package/components/ServiceProviderInvitationPrompt.vue +155 -0
  176. package/components/ServiceProviderMain.vue +601 -432
  177. package/components/Signature.vue +40 -34
  178. package/components/SignaturePad.vue +5 -29
  179. package/components/SiteSettings.vue +41 -11
  180. package/components/SiteSettingsWorkOrder.vue +61 -30
  181. package/components/SlideCardGroup.vue +35 -11
  182. package/components/Snackbar.vue +36 -3
  183. package/components/SpecificAttr.vue +18 -8
  184. package/components/StatusChip.vue +156 -0
  185. package/components/StockCard.vue +17 -12
  186. package/components/SubscriptionPlanTable.vue +207 -140
  187. package/components/SwitchContext.vue +201 -9
  188. package/components/TableHygiene.vue +36 -46
  189. package/components/TableList.vue +10 -10
  190. package/components/TableListSecondary.vue +105 -54
  191. package/components/TableMain.vue +251 -78
  192. package/components/TableV3.vue +61 -70
  193. package/components/TiptapEditor.vue +33 -40
  194. package/components/TiptapEditorQr.vue +5 -2
  195. package/components/Tooltip/Info.vue +69 -24
  196. package/components/UnitMain.vue +40 -60
  197. package/components/UnitPersonCard.vue +42 -13
  198. package/components/VehicleAddSelection.vue +48 -32
  199. package/components/VehicleForm.vue +112 -129
  200. package/components/VehicleManagement.vue +26 -12
  201. package/components/VehicleUpdateMoreAction.vue +59 -46
  202. package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
  203. package/components/VisitorForm.vue +774 -210
  204. package/components/VisitorFormSelection.vue +40 -37
  205. package/components/VisitorManagement.vue +239 -82
  206. package/components/VisitorPassKeyQRScanner.vue +65 -18
  207. package/components/VisitorSearchResultSummary.vue +60 -16
  208. package/components/VisitorSocketPopUp.vue +22 -14
  209. package/components/VisitorsReportPreview.vue +44 -29
  210. package/components/WorkOrder/Create.vue +26 -17
  211. package/components/WorkOrder/ListView.vue +38 -7
  212. package/components/WorkOrder/Main.vue +62 -59
  213. package/composables/useFeedback.ts +3 -3
  214. package/composables/useHidAmico.ts +269 -0
  215. package/composables/useHidNavigation.ts +2 -9
  216. package/composables/useMember.ts +3 -3
  217. package/composables/useNFCPatrolDailyReport.ts +13 -0
  218. package/composables/useNFCPatrolMonthlyReport.ts +22 -56
  219. package/composables/useNFCPatrolReport.ts +13 -0
  220. package/composables/useNotificationPreference.ts +87 -0
  221. package/composables/useOnlineFormPages.ts +2 -0
  222. package/composables/useRole.ts +22 -0
  223. package/composables/useServiceProvider.ts +3 -3
  224. package/composables/useServiceProviderInvitation.ts +145 -0
  225. package/composables/useSipWebPhone.ts +336 -0
  226. package/composables/useSubscriptionPlan.ts +62 -0
  227. package/composables/useThemePreference.ts +63 -0
  228. package/composables/useVisitor.ts +6 -1
  229. package/composables/useWebUsb.ts +127 -37
  230. package/composables/useWorkOrder.ts +17 -3
  231. package/error.vue +30 -6
  232. package/nuxt.config.ts +36 -0
  233. package/package.json +25 -4
  234. package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
  235. package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
  236. package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
  237. package/pages/notification-settings.vue +19 -0
  238. package/pages/payment-method-linked.vue +34 -10
  239. package/pages/require-customer.vue +48 -10
  240. package/pages/require-organization-membership.vue +32 -8
  241. package/pages/unauthorized.vue +29 -4
  242. package/plugins/secure-member.client.ts +21 -56
  243. package/plugins/vuetify.ts +69 -2
  244. package/types/nfc-patrol-report.ts +3 -2
  245. package/types/site.d.ts +71 -0
  246. package/types/subscription.ts +18 -11
  247. package/utils/breadcrumb.test.ts +108 -0
  248. package/utils/breadcrumb.ts +82 -0
  249. package/utils/dashboard.test.ts +118 -0
  250. package/utils/dashboard.ts +53 -0
  251. package/utils/data.test.ts +95 -0
  252. package/utils/data.ts +40 -2
  253. package/utils/hid-discover.test.ts +102 -0
  254. package/utils/list-page.test.ts +60 -0
  255. package/utils/list-page.ts +38 -0
  256. package/utils/materialSymbols.ts +363 -0
  257. package/utils/nav-sections.test.ts +190 -0
  258. package/utils/nav-sections.ts +151 -0
  259. package/utils/nfc-patrol-report.test.ts +130 -0
  260. package/utils/nfc-patrol-report.ts +101 -0
  261. package/utils/role.test.ts +69 -0
  262. package/utils/role.ts +10 -0
  263. package/utils/route-name.test.ts +85 -0
  264. package/utils/route-name.ts +24 -0
  265. package/utils/status.test.ts +110 -0
  266. package/utils/status.ts +135 -0
  267. package/utils/table-card.test.ts +84 -0
  268. package/utils/theme-aa-ledger.ts +148 -0
  269. package/utils/theme.test.ts +485 -7
  270. package/utils/theme.ts +295 -110
  271. package/.changeset/README.md +0 -8
  272. package/.changeset/config.json +0 -11
  273. package/.editorconfig +0 -12
  274. package/.github/workflows/main.yml +0 -17
  275. package/.github/workflows/publish-staging.yml +0 -47
  276. package/.github/workflows/publish.yml +0 -39
  277. package/PUBLISHING.md +0 -269
  278. package/components/HidIdentityMapping.vue +0 -975
  279. package/components/ScheduleTaskAreaFormDialog.vue +0 -144
  280. package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
  281. package/components/ScheduleTastTicketMain.vue +0 -182
  282. package/components/TableWithButton.vue +0 -94
  283. package/test/visitor-socket.test.mjs +0 -36
@@ -1,29 +1,15 @@
1
1
  <template>
2
- <v-card class="pa-2">
2
+ <v-card class="screen-modal">
3
3
  <!-- Header -->
4
- <v-card-title class="d-flex align-center justify-space-between pb-2">
5
- <span class="text-h6 font-weight-bold">
6
- {{ isEditing ? "Edit Plan" : "Create New Plan" }}
7
- </span>
8
-
9
- <v-btn
10
- icon="mdi-close"
11
- variant="text"
12
- density="comfortable"
13
- aria-label="Close"
14
- @click="$emit('cancel')"
15
- />
4
+ <v-card-title>
5
+ {{ isEditing ? "Edit Plan" : "Create New Plan" }}
16
6
  </v-card-title>
17
7
 
18
- <v-divider />
19
-
20
8
  <!-- Form Content -->
21
- <v-form ref="formRef" class="my-4" @submit.prevent="submit">
22
- <v-card-text class="pa-2 max-h-70vh overflow-y-auto">
9
+ <v-form ref="formRef" @submit.prevent="submit">
10
+ <v-card-text class="screen-modal__body px-6 pt-4">
23
11
  <!-- Plan Details Section -->
24
- <div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
25
- Plan Details
26
- </div>
12
+ <div class="plan-form__section">Plan Details</div>
27
13
 
28
14
  <!-- Plan Name -->
29
15
  <v-text-field
@@ -32,8 +18,8 @@
32
18
  placeholder="e.g., Business"
33
19
  variant="outlined"
34
20
  density="compact"
21
+ class="filter-field mb-3"
35
22
  :error-messages="errors.name"
36
- class="mb-3"
37
23
  required
38
24
  >
39
25
  <template #label>
@@ -48,9 +34,9 @@
48
34
  placeholder="Enter description"
49
35
  variant="outlined"
50
36
  density="compact"
37
+ class="filter-field mb-3"
51
38
  rows="2"
52
39
  auto-grow
53
- class="mb-3"
54
40
  />
55
41
 
56
42
  <!-- Max Seats -->
@@ -60,7 +46,7 @@
60
46
  min="0"
61
47
  variant="outlined"
62
48
  density="compact"
63
- class="mb-4"
49
+ class="filter-field mb-4"
64
50
  required
65
51
  >
66
52
  <template #label>
@@ -68,12 +54,8 @@
68
54
  </template>
69
55
  </v-text-field>
70
56
 
71
- <v-divider class="my-4" />
72
-
73
57
  <!-- Pricing Section -->
74
- <div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
75
- Pricing
76
- </div>
58
+ <div class="plan-form__section">Pricing</div>
77
59
 
78
60
  <!-- Plan Type -->
79
61
  <v-select
@@ -81,7 +63,7 @@
81
63
  :items="planTypeOptions"
82
64
  variant="outlined"
83
65
  density="compact"
84
- class="mb-3"
66
+ class="filter-field mb-3"
85
67
  >
86
68
  <template #label>
87
69
  Subscription plan type <span class="text-error">*</span>
@@ -96,7 +78,7 @@
96
78
  placeholder="Select billing cycle"
97
79
  variant="outlined"
98
80
  density="compact"
99
- class="mb-3"
81
+ class="filter-field mb-3"
100
82
  >
101
83
  <template #label>
102
84
  Billing cycle <span class="text-error">*</span>
@@ -105,25 +87,23 @@
105
87
 
106
88
  <!-- Bundle Applications (Chip Toggle) -->
107
89
  <div v-if="form.planType !== 'custom_apps'" class="mb-4">
108
- <div class="text-caption font-weight-medium text-grey-darken-2 mb-2">
109
- Included Applications
110
- </div>
90
+ <div class="plan-form__label mb-2">Included Applications</div>
111
91
 
112
92
  <v-chip-group
113
93
  v-model="form.bundleApps"
114
94
  column
115
95
  multiple
116
- selected-class="bg-grey-darken-3 text-white"
96
+ selected-class="plan-form__chip--on"
117
97
  >
118
98
  <v-chip
119
- v-for="app in AVAILABLE_APPLICATIONS"
120
- :key="app.id"
121
- :value="app.id"
99
+ v-for="app in APP_LIST"
100
+ :key="app.value"
101
+ :value="app.value"
122
102
  filter
123
103
  variant="outlined"
124
104
  size="small"
125
105
  >
126
- {{ app.name }}
106
+ {{ app.title }}
127
107
  </v-chip>
128
108
  </v-chip-group>
129
109
  </div>
@@ -139,7 +119,7 @@
139
119
  :suffix="form.billingCycle === 'annually' ? '/yr' : '/mo'"
140
120
  variant="outlined"
141
121
  density="compact"
142
- class="mb-3"
122
+ class="filter-field mb-3"
143
123
  >
144
124
  <template #label>
145
125
  Price <span class="text-error">*</span>
@@ -148,53 +128,60 @@
148
128
 
149
129
  <!-- Custom Applications (List Checkbox) -->
150
130
  <div v-if="form.planType === 'custom_apps'" class="mb-4">
151
- <div class="text-caption font-weight-medium text-grey-darken-2 mb-1">
131
+ <div class="plan-form__label mb-1">
152
132
  Included Applications <span class="text-error">*</span>
153
133
  </div>
154
134
 
155
135
  <v-card variant="outlined" class="pa-1 mb-3">
156
136
  <v-list density="compact" class="py-0">
157
137
  <v-list-item
158
- v-for="app in AVAILABLE_APPLICATIONS"
159
- :key="app.id"
138
+ v-for="app in APP_LIST"
139
+ :key="app.value"
160
140
  class="px-2"
161
141
  >
162
142
  <template #prepend>
163
143
  <v-checkbox-btn
164
- :model-value="form.customApps.includes(app.id)"
144
+ :model-value="form.customApps.includes(app.value)"
165
145
  density="compact"
166
- @update:model-value="toggleApp(form.customApps, app.id)"
146
+ @update:model-value="toggleApp(form.customApps, app.value)"
167
147
  />
168
148
  </template>
169
149
 
170
150
  <v-list-item-title class="text-body-2">
171
- {{ app.name }}
151
+ {{ app.title }}
172
152
  </v-list-item-title>
173
153
 
174
154
  <template #append>
175
- <span class="text-caption text-grey">
176
- ${{ form.billingCycle === 'annually' ? app.yearlyPrice : app.monthlyPrice }}/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}
177
- </span>
155
+ <div class="d-flex align-center" style="gap: 4px">
156
+ <span class="plan-form__unit">$</span>
157
+ <v-text-field
158
+ v-model.number="form.customAppPrices[app.value]"
159
+ type="number"
160
+ min="0"
161
+ density="compact"
162
+ variant="outlined"
163
+ hide-details
164
+ style="width: 84px"
165
+ :disabled="!form.customApps.includes(app.value)"
166
+ />
167
+ <span class="plan-form__unit">/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}</span>
168
+ </div>
178
169
  </template>
179
170
  </v-list-item>
180
171
  </v-list>
181
172
  </v-card>
182
173
 
183
174
  <!-- Custom Total Price Display -->
184
- <v-sheet
185
- color="grey-lighten-4"
186
- rounded
187
- class="pa-3 d-flex align-center justify-space-between mb-1"
188
- >
189
- <span class="text-caption text-grey-darken-2">
175
+ <div class="plan-form__total mb-1">
176
+ <span class="plan-form__label">
190
177
  Total price {{ form.billingCycle === 'annually' ? '/ year' : '/ month' }}
191
178
  </span>
192
- <span class="text-subtitle-1 font-weight-bold">
179
+ <span class="plan-form__total-value">
193
180
  ${{ customTotalPrice.toLocaleString() }}
194
181
  </span>
195
- </v-sheet>
182
+ </div>
196
183
 
197
- <p class="text-caption text-grey">
184
+ <p class="plan-form__unit">
198
185
  Calculated automatically from the selected applications.
199
186
  </p>
200
187
  </div>
@@ -211,27 +198,20 @@
211
198
  </v-alert>
212
199
  </v-card-text>
213
200
 
214
- <v-divider />
215
-
216
201
  <!-- Footer Buttons -->
217
- <v-card-actions class="pt-3 px-2">
218
- <v-btn
219
- variant="outlined"
220
- color="grey-darken-1"
221
- class="flex-1 text-none"
222
- @click="$emit('cancel')"
223
- >
224
- Cancel
225
- </v-btn>
226
-
227
- <v-btn
228
- type="submit"
229
- color="grey-darken-4"
230
- variant="flat"
231
- class="flex-1 text-none"
232
- >
233
- {{ isEditing ? "Save Changes" : "Create Plan" }}
234
- </v-btn>
202
+ <v-card-actions class="screen-modal__footer">
203
+ <v-row no-gutters>
204
+ <v-col cols="6" class="pr-2">
205
+ <v-btn block variant="outlined" class="text-none screen-btn-ghost" @click="$emit('cancel')">
206
+ Cancel
207
+ </v-btn>
208
+ </v-col>
209
+ <v-col cols="6" class="pl-2">
210
+ <v-btn block type="submit" variant="flat" class="text-none screen-btn-primary">
211
+ {{ isEditing ? "Save Changes" : "Create Plan" }}
212
+ </v-btn>
213
+ </v-col>
214
+ </v-row>
235
215
  </v-card-actions>
236
216
  </v-form>
237
217
  </v-card>
@@ -241,7 +221,9 @@
241
221
  import { reactive, computed, watch } from "vue";
242
222
 
243
223
  import {
244
- AVAILABLE_APPLICATIONS,
224
+ APP_LIST,
225
+ STATIC_APP_MONTHLY_PRICE,
226
+ STATIC_APP_YEARLY_PRICE,
245
227
  type PlanFormState,
246
228
  type SubscriptionPlan,
247
229
  } from "../types/subscription";
@@ -284,6 +266,14 @@ const form = reactive<PlanFormState>({
284
266
  props.initialPlan?.planType === "custom_apps"
285
267
  ? [...(props.initialPlan?.applicationIds ?? [])]
286
268
  : [],
269
+ customAppPrices: Object.fromEntries(
270
+ APP_LIST.map((app) => [
271
+ app.value,
272
+ props.initialPlan?.billingCycle === "annually"
273
+ ? STATIC_APP_YEARLY_PRICE
274
+ : STATIC_APP_MONTHLY_PRICE,
275
+ ])
276
+ ),
287
277
  });
288
278
 
289
279
  const errors = reactive({
@@ -304,6 +294,29 @@ watch(
304
294
  }
305
295
  );
306
296
 
297
+ function staticPriceForCycle(cycle: typeof form.billingCycle) {
298
+ return cycle === "annually" ? STATIC_APP_YEARLY_PRICE : STATIC_APP_MONTHLY_PRICE;
299
+ }
300
+
301
+ /*
302
+ * Keep custom app prices in sync with the billing cycle, but only for
303
+ * apps still sitting at the previous cycle's default (leave manual edits alone)
304
+ */
305
+ watch(
306
+ () => form.billingCycle,
307
+ (newCycle, oldCycle) => {
308
+ const oldDefault = staticPriceForCycle(oldCycle);
309
+ const newDefault = staticPriceForCycle(newCycle);
310
+ if (oldDefault === newDefault) return;
311
+
312
+ for (const app of APP_LIST) {
313
+ if (form.customAppPrices[app.value] === oldDefault) {
314
+ form.customAppPrices[app.value] = newDefault;
315
+ }
316
+ }
317
+ }
318
+ );
319
+
307
320
  /*
308
321
  * Toggle application selection helper
309
322
  */
@@ -320,15 +333,10 @@ function toggleApp(list: string[], id: string) {
320
333
  * Custom app total price calculation
321
334
  */
322
335
  const customTotalPrice = computed(() => {
323
- return form.customApps.reduce((total, id) => {
324
- const app = AVAILABLE_APPLICATIONS.find((item) => item.id === id);
325
- if (!app) return total;
326
-
327
- return (
328
- total +
329
- (form.billingCycle === "annually" ? app.yearlyPrice : app.monthlyPrice)
330
- );
331
- }, 0);
336
+ return form.customApps.reduce(
337
+ (total, value) => total + (form.customAppPrices[value] ?? 0),
338
+ 0
339
+ );
332
340
  });
333
341
 
334
342
  /*
@@ -340,6 +348,11 @@ function validate(): boolean {
340
348
 
341
349
  if (form.planType !== "free_bundle" && !form.billingCycle) {
342
350
  errors.pricing = "Please select a billing cycle.";
351
+ } else if (
352
+ (form.planType === "free_bundle" || form.planType === "paid_bundle") &&
353
+ form.bundleApps.length === 0
354
+ ) {
355
+ errors.pricing = "Select at least one application.";
343
356
  } else if (
344
357
  form.planType === "paid_bundle" &&
345
358
  (!form.bundlePrice || form.bundlePrice <= 0)
@@ -370,7 +383,53 @@ function submit() {
370
383
  </script>
371
384
 
372
385
  <style scoped>
373
- .max-h-70vh {
374
- max-height: 70vh;
386
+ /* The design's section label: the same all-caps rule the rest of the estate
387
+ uses, instead of Vuetify's `text-subtitle-2 text-grey-darken-3`, which is a
388
+ fixed grey that does not follow the theme. */
389
+ .plan-form__section {
390
+ font-size: var(--fs-section-label);
391
+ font-weight: var(--fw-section-label);
392
+ letter-spacing: var(--ls-section-label);
393
+ text-transform: uppercase;
394
+ color: var(--muted);
395
+ margin-bottom: 12px;
396
+ }
397
+
398
+ .plan-form__label {
399
+ font-size: var(--fs-cell-dense);
400
+ font-weight: 600;
401
+ color: var(--text2);
402
+ }
403
+
404
+ .plan-form__unit {
405
+ font-size: var(--fs-chip-lg);
406
+ font-weight: 600;
407
+ color: var(--muted);
408
+ }
409
+
410
+ /* Was `<v-sheet color="grey-lighten-4">`, a light-theme-only grey that stayed
411
+ light in dark mode. */
412
+ .plan-form__total {
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: space-between;
416
+ padding: 12px;
417
+ border-radius: var(--r-inner);
418
+ background: var(--hover);
419
+ }
420
+
421
+ .plan-form__total-value {
422
+ font-size: var(--fs-card-title);
423
+ font-weight: var(--fw-card-title);
424
+ color: var(--text);
425
+ }
426
+
427
+ /* The selected application chip. The handoff draws no chip GROUP, so this is
428
+ the estate's soft-accent selected tone rather than an invented one - it
429
+ replaces `bg-grey-darken-3 text-white`, which was a fixed dark grey. */
430
+ .plan-form__chip--on {
431
+ background: var(--accent-soft) !important;
432
+ color: var(--accent-text) !important;
433
+ border-color: var(--accent) !important;
375
434
  }
376
435
  </style>
@@ -1,10 +1,28 @@
1
+ <!--
2
+ "No data to display" and "we could not fetch it" are not the same sentence.
3
+
4
+ This component drew the first one for both, because every dashboard request
5
+ defaulted a rejection to an empty list. `error` splits them: a genuine zero
6
+ keeps the neutral wording, a failed request says so in the error tone and
7
+ tells the reader the figure is missing rather than nil.
8
+ -->
1
9
  <template>
2
10
  <div
3
- class="d-flex flex-column align-center justify-center text-center text-blue-grey-lighten-1"
11
+ class="d-flex flex-column align-center justify-center text-center dashboard-empty"
12
+ :class="{ 'dashboard-empty--error': error }"
4
13
  :style="{ minHeight: compact ? '72px' : '140px', gap: '8px' }"
14
+ role="status"
5
15
  >
6
- <v-icon icon="mdi-database-search-outline" size="28" />
7
- <span class="text-caption font-weight-bold">No data to display</span>
16
+ <v-icon
17
+ :icon="error ? 'mdi-cloud-alert-outline' : 'mdi-database-search-outline'"
18
+ size="28"
19
+ />
20
+ <span class="text-caption font-weight-bold">
21
+ {{ error ? "Couldn't load this data" : "No data to display" }}
22
+ </span>
23
+ <span v-if="error" class="text-caption dashboard-empty__hint">
24
+ The request failed — this is not a zero. Refresh to try again.
25
+ </span>
8
26
  </div>
9
27
  </template>
10
28
 
@@ -14,5 +32,32 @@ defineProps({
14
32
  type: Boolean,
15
33
  default: false,
16
34
  },
35
+ /** The request behind this panel failed - say so instead of showing "none". */
36
+ error: {
37
+ type: Boolean,
38
+ default: false,
39
+ },
17
40
  });
18
41
  </script>
42
+
43
+ <style scoped>
44
+ /* Was `text-blue-grey-lighten-1`, a fixed Vuetify grey that measured 2.4:1 in
45
+ LIGHT mode - the empty state was the hardest thing on the page to read. The
46
+ muted token is the design's own and follows the theme. */
47
+ .dashboard-empty {
48
+ font-family: var(--font-sans);
49
+ color: var(--muted);
50
+ }
51
+
52
+ /* `--err` is the design's own error tone and is AA in both themes; the hint
53
+ line stays on `--text2` so the explanation is never the palest thing here
54
+ (a dimmed explanation is the one line that must stay readable). */
55
+ .dashboard-empty--error {
56
+ color: var(--err);
57
+ }
58
+
59
+ .dashboard-empty__hint {
60
+ color: var(--text2);
61
+ max-width: 34ch;
62
+ }
63
+ </style>