@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.
- package/.playground/app.vue +7 -2
- package/.playground/pages/feedback.vue +30 -0
- package/CHANGELOG.md +12 -0
- package/components/Chat/Bubbles.vue +53 -0
- package/components/Chat/Information.vue +187 -0
- package/components/Chat/ListCard.vue +62 -0
- package/components/Chat/Message.vue +149 -0
- package/components/Chat/Navigation.vue +150 -0
- package/components/ConfirmDialog.vue +66 -0
- package/components/Container/Standard.vue +33 -0
- package/components/Feedback/Form.vue +136 -0
- package/components/FeedbackDetail.vue +465 -0
- package/components/FeedbackMain.vue +454 -0
- package/components/FormDialog.vue +65 -0
- package/components/Input/File.vue +203 -0
- package/components/Input/ListGroupSelection.vue +96 -0
- package/components/Input/NewDate.vue +123 -0
- package/components/Input/Number.vue +124 -0
- package/components/InvitationMain.vue +284 -0
- package/components/Layout/Header.vue +14 -4
- package/components/ListView.vue +87 -0
- package/components/MemberMain.vue +459 -0
- package/components/RolePermissionFormCreate.vue +161 -0
- package/components/RolePermissionFormPreviewUpdate.vue +183 -0
- package/components/RolePermissionMain.vue +361 -0
- package/components/ServiceProviderFormCreate.vue +154 -0
- package/components/ServiceProviderMain.vue +195 -0
- package/components/SignaturePad.vue +73 -0
- package/components/SpecificAttr.vue +53 -0
- package/components/SwitchContext.vue +26 -5
- package/components/TableList.vue +150 -0
- package/components/TableListSecondary.vue +164 -0
- package/components/WorkOrder/Create.vue +197 -0
- package/components/WorkOrder/ListView.vue +96 -0
- package/components/WorkOrder/Main.vue +308 -0
- package/components/Workorder.vue +1 -0
- package/composables/useAddress.ts +107 -0
- package/composables/useCommonPermission.ts +130 -0
- package/composables/useCustomer.ts +113 -0
- package/composables/useFeedback.ts +117 -0
- package/composables/useFile.ts +40 -0
- package/composables/useInvoice.ts +18 -0
- package/composables/useLocal.ts +24 -4
- package/composables/useLocalAuth.ts +62 -20
- package/composables/useLocalSetup.ts +13 -0
- package/composables/useMember.ts +111 -0
- package/composables/useOrg.ts +76 -92
- package/composables/usePaymentMethod.ts +101 -0
- package/composables/usePrice.ts +15 -0
- package/composables/usePromoCode.ts +36 -0
- package/composables/useRole.ts +38 -7
- package/composables/useServiceProvider.ts +218 -0
- package/composables/useSite.ts +108 -0
- package/composables/useSubscription.ts +149 -0
- package/composables/useUser.ts +38 -14
- package/composables/useUtils.ts +218 -6
- package/composables/useVerification.ts +33 -0
- package/composables/useWorkOrder.ts +68 -0
- package/middleware/01.auth.ts +11 -0
- package/middleware/02.org.ts +18 -0
- package/middleware/03.customer.ts +13 -0
- package/middleware/member.ts +4 -0
- package/nuxt.config.ts +3 -1
- package/package.json +7 -3
- package/pages/index.vue +3 -0
- package/pages/payment-method-linked.vue +31 -0
- package/pages/require-customer.vue +56 -0
- package/pages/require-organization-membership.vue +47 -0
- package/pages/unauthorized.vue +29 -0
- package/plugins/API.ts +2 -25
- package/plugins/iconify.client.ts +5 -0
- package/plugins/secure-member.client.ts +54 -0
- package/plugins/vuetify.ts +2 -0
- package/public/bg-camera.jpg +0 -0
- package/public/bg-city.jpg +0 -0
- package/public/bg-condo.jpg +0 -0
- package/public/images/icons/delete-icon.png +0 -0
- package/public/sprite.svg +1 -0
- package/types/address.d.ts +13 -0
- package/types/customer.d.ts +15 -0
- package/types/feedback.d.ts +63 -0
- package/types/local.d.ts +47 -38
- package/types/member.d.ts +21 -0
- package/types/org.d.ts +13 -0
- package/types/permission.d.ts +1 -0
- package/types/price.d.ts +17 -0
- package/types/promo-code.d.ts +19 -0
- package/types/service-provider.d.ts +15 -0
- package/types/site.d.ts +13 -0
- package/types/subscription.d.ts +23 -0
- package/types/user.d.ts +19 -0
- package/types/verification.d.ts +20 -0
- package/types/work-order.d.ts +40 -0
package/composables/useUtils.ts
CHANGED
|
@@ -6,6 +6,35 @@ export default function useUtils() {
|
|
|
6
6
|
return regex.test(v) || "Please enter a valid email address";
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
const validateDate = (value: string): boolean | string => {
|
|
10
|
+
const dateRegex = /^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/;
|
|
11
|
+
|
|
12
|
+
if (!dateRegex.test(value)) return "Invalid date format (MM/DD/YYYY)";
|
|
13
|
+
|
|
14
|
+
return true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function requireListRule(value: string[]) {
|
|
18
|
+
return value.length ? "" : "Required";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const minOneMonthAdvance = (value: string): boolean | string => {
|
|
22
|
+
if (!/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/.test(value)) {
|
|
23
|
+
return "Invalid date format (MM/DD/YYYY)";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const [month, day, year] = value.split("/").map(Number);
|
|
27
|
+
const selectedDate = new Date(year, month - 1, day);
|
|
28
|
+
|
|
29
|
+
const currentDate = new Date();
|
|
30
|
+
const minDate = new Date();
|
|
31
|
+
minDate.setMonth(currentDate.getMonth() + 1); // 1 month in advance
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
selectedDate >= minDate || "Date must be at least 1 month in advance"
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
9
38
|
const passwordRule = (v: string) =>
|
|
10
39
|
v.length >= 8 || "Password must be at least 8 characters long";
|
|
11
40
|
|
|
@@ -26,6 +55,11 @@ export default function useUtils() {
|
|
|
26
55
|
}
|
|
27
56
|
};
|
|
28
57
|
|
|
58
|
+
const validateWord = (value: string): boolean | string => {
|
|
59
|
+
if (value.trim().length < 3) return "Must be at least 3 characters";
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
29
63
|
const validatePassword = (
|
|
30
64
|
password: Ref<string>,
|
|
31
65
|
passwordErrors: Ref<string[]>
|
|
@@ -66,13 +100,16 @@ export default function useUtils() {
|
|
|
66
100
|
return ""; // Default fallback for invalid input
|
|
67
101
|
}
|
|
68
102
|
|
|
69
|
-
const
|
|
70
|
-
.trim()
|
|
71
|
-
.split(/\s+/) // Split by spaces
|
|
72
|
-
.map((word) => word[0]?.toUpperCase()) // Take the first letter of each word and capitalize
|
|
73
|
-
.join(""); // Combine all initials
|
|
103
|
+
const words = name.trim().split(/\s+/); // Split by spaces
|
|
74
104
|
|
|
75
|
-
|
|
105
|
+
if (words.length === 1) {
|
|
106
|
+
return words[0].slice(0, 2).toUpperCase(); // Take first two letters of the single word
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return words
|
|
110
|
+
.map((word) => word[0]?.toUpperCase()) // Take the first letter of each word and capitalize
|
|
111
|
+
.join("")
|
|
112
|
+
.slice(0, 2); // Limit to 2 characters
|
|
76
113
|
}
|
|
77
114
|
|
|
78
115
|
function getDimensions(
|
|
@@ -93,12 +130,171 @@ export default function useUtils() {
|
|
|
93
130
|
});
|
|
94
131
|
}
|
|
95
132
|
|
|
133
|
+
async function getCountries() {
|
|
134
|
+
try {
|
|
135
|
+
const countries = await useNuxtApp().$api<Array<Record<string, any>>>(
|
|
136
|
+
"https://restcountries.com/v3.1/all?fields=name,idd",
|
|
137
|
+
{ method: "GET" }
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const uniqueCountries = new Map();
|
|
141
|
+
|
|
142
|
+
countries.forEach((country) => {
|
|
143
|
+
let suffixes = country.idd?.suffixes?.[0] || "";
|
|
144
|
+
let code = `${country.idd?.root || ""}${suffixes}`;
|
|
145
|
+
let name = country.name?.common || "";
|
|
146
|
+
|
|
147
|
+
if (name && code && !uniqueCountries.has(code)) {
|
|
148
|
+
uniqueCountries.set(code, { index: `${name}-${code}`, name, code });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return Array.from(uniqueCountries.values()).sort((a, b) =>
|
|
153
|
+
a.name.localeCompare(b.name)
|
|
154
|
+
);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(error);
|
|
157
|
+
throw new Error("Failed to fetch countries.");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const formatter = new Intl.NumberFormat("en-US", {
|
|
162
|
+
minimumFractionDigits: 2,
|
|
163
|
+
maximumFractionDigits: 2,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
function formatNumber(
|
|
167
|
+
amount: number,
|
|
168
|
+
options: {
|
|
169
|
+
currency?: string;
|
|
170
|
+
locale?: string;
|
|
171
|
+
useSymbol?: boolean;
|
|
172
|
+
decimalPlaces?: number;
|
|
173
|
+
} = {}
|
|
174
|
+
): string {
|
|
175
|
+
const {
|
|
176
|
+
currency,
|
|
177
|
+
locale = "en-US",
|
|
178
|
+
useSymbol = false,
|
|
179
|
+
decimalPlaces = 2,
|
|
180
|
+
} = options;
|
|
181
|
+
|
|
182
|
+
return new Intl.NumberFormat(locale, {
|
|
183
|
+
style: useSymbol && currency ? "currency" : "decimal",
|
|
184
|
+
currency: currency || "USD", // Default currency (ignored if `useSymbol` is false)
|
|
185
|
+
minimumFractionDigits: decimalPlaces,
|
|
186
|
+
maximumFractionDigits: decimalPlaces,
|
|
187
|
+
}).format(amount);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 🔹 Examples:
|
|
191
|
+
// console.log(formatNumber(1234.56)); // "1,234.56" (comma separator, 2 decimals)
|
|
192
|
+
// console.log(formatNumber(1234.56, { decimalPlaces: 0 })); // "1,234" (no decimals)
|
|
193
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "USD" })); // "$1,234.56"
|
|
194
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "SGD", decimalPlaces: 0 })); // "S$1,234"
|
|
195
|
+
// console.log(formatNumber(1234.56, { useSymbol: false, decimalPlaces: 0 })); // "1,234"
|
|
196
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE" })); // "1.234,56 €"
|
|
197
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE", decimalPlaces: 0 })); // "1.234 €"
|
|
198
|
+
|
|
199
|
+
function convertPermissionsToArray(permissions: TPermissions) {
|
|
200
|
+
return Object.entries(permissions).flatMap(([resource, actions]) =>
|
|
201
|
+
Object.keys(actions).map((action) => `${resource}:${action}`)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
96
205
|
function redirect(link: string, page?: string) {
|
|
97
206
|
const href = page ? `${link}/${page}` : link;
|
|
98
207
|
|
|
99
208
|
window.location.href = href;
|
|
100
209
|
}
|
|
101
210
|
|
|
211
|
+
function computeTieredCost(seats: number, tiers: TPromoTier[]) {
|
|
212
|
+
let totalCost = 0;
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
215
|
+
let { min, max, price } = tiers[i];
|
|
216
|
+
|
|
217
|
+
if (max === 0) max = Infinity; // Handle unlimited tier
|
|
218
|
+
|
|
219
|
+
if (seats >= min) {
|
|
220
|
+
let seatsInTier = Math.min(seats, max) - min + 1;
|
|
221
|
+
totalCost += seatsInTier * price;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (seats <= max) break; // Stop when all seats are counted
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return totalCost;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const getColorStatus = (status: string) => {
|
|
231
|
+
switch (status.toLowerCase()) {
|
|
232
|
+
case "in-progress":
|
|
233
|
+
case "in progress":
|
|
234
|
+
return "warning";
|
|
235
|
+
case "to-do":
|
|
236
|
+
case "to do":
|
|
237
|
+
return "#052439";
|
|
238
|
+
case "complete":
|
|
239
|
+
case "active":
|
|
240
|
+
return "success";
|
|
241
|
+
|
|
242
|
+
case "suspended":
|
|
243
|
+
return "error";
|
|
244
|
+
|
|
245
|
+
default:
|
|
246
|
+
return "grey";
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
function getOrigin() {
|
|
251
|
+
if (process.client) {
|
|
252
|
+
return window.location.origin;
|
|
253
|
+
}
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const search = useState("search", () => "");
|
|
258
|
+
|
|
259
|
+
function formatDate(dateString: string) {
|
|
260
|
+
if (!dateString) return "N/A";
|
|
261
|
+
|
|
262
|
+
const date = new Date(dateString);
|
|
263
|
+
return date.toLocaleDateString("en-US", {
|
|
264
|
+
year: "numeric",
|
|
265
|
+
month: "short",
|
|
266
|
+
day: "numeric",
|
|
267
|
+
hour: "2-digit",
|
|
268
|
+
minute: "2-digit",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function formatNature(value: string): string {
|
|
273
|
+
if (!value) return "";
|
|
274
|
+
return value
|
|
275
|
+
.split("_")
|
|
276
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
277
|
+
.join(" ");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function replaceMatch(str: string, match: string, replace: string) {
|
|
281
|
+
return str.replace(new RegExp(match, "g"), replace);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function setRouteParams(...args: Array<Record<string, string>>) {
|
|
285
|
+
const params: Record<string, string> = {};
|
|
286
|
+
|
|
287
|
+
args.forEach((arg) => {
|
|
288
|
+
Object.entries(arg).forEach(([key, value]) => {
|
|
289
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
290
|
+
params[key] = value;
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return params;
|
|
296
|
+
}
|
|
297
|
+
|
|
102
298
|
return {
|
|
103
299
|
requiredRule,
|
|
104
300
|
emailRule,
|
|
@@ -111,6 +307,22 @@ export default function useUtils() {
|
|
|
111
307
|
insertBetween,
|
|
112
308
|
getNameInitials,
|
|
113
309
|
getDimensions,
|
|
310
|
+
validateWord,
|
|
311
|
+
getCountries,
|
|
312
|
+
formatter,
|
|
313
|
+
formatNumber,
|
|
314
|
+
validateDate,
|
|
315
|
+
minOneMonthAdvance,
|
|
316
|
+
requireListRule,
|
|
317
|
+
convertPermissionsToArray,
|
|
114
318
|
redirect,
|
|
319
|
+
computeTieredCost,
|
|
320
|
+
getColorStatus,
|
|
321
|
+
getOrigin,
|
|
322
|
+
search,
|
|
323
|
+
formatDate,
|
|
324
|
+
formatNature,
|
|
325
|
+
replaceMatch,
|
|
326
|
+
setRouteParams,
|
|
115
327
|
};
|
|
116
328
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default function useVerification() {
|
|
2
|
+
function getVerifications({
|
|
3
|
+
status = "pending",
|
|
4
|
+
type = "user-invite",
|
|
5
|
+
search = "",
|
|
6
|
+
page = 1,
|
|
7
|
+
email = "",
|
|
8
|
+
} = {}): Promise<{
|
|
9
|
+
items: TMiniVerification[];
|
|
10
|
+
pages: number;
|
|
11
|
+
pageRange: string;
|
|
12
|
+
}> {
|
|
13
|
+
return useNuxtApp().$api("/api/verifications", {
|
|
14
|
+
method: "GET",
|
|
15
|
+
query: { status, search, page, type, email },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cancelUserInvitation(id: string) {
|
|
20
|
+
console.log("Calling API to cancel:", id);
|
|
21
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
22
|
+
`/api/verifications/${id}/cancel`,
|
|
23
|
+
{
|
|
24
|
+
method: "PUT",
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
getVerifications,
|
|
31
|
+
cancelUserInvitation,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export default function useFeedback() {
|
|
2
|
+
const workOrders = useState<Array<TWorkOrder>>("workOrders", () => []);
|
|
3
|
+
const page = useState("page", () => 1);
|
|
4
|
+
const pages = useState("pages", () => 0);
|
|
5
|
+
const pageRange = useState("pageRange", () => "-- - -- of --");
|
|
6
|
+
const workOrder = useState<TWorkOrder>("workOrder", () => ({
|
|
7
|
+
_id: "",
|
|
8
|
+
subject: "",
|
|
9
|
+
description: "",
|
|
10
|
+
createdBy: "",
|
|
11
|
+
service: "",
|
|
12
|
+
provider: "",
|
|
13
|
+
organization: "",
|
|
14
|
+
site: "",
|
|
15
|
+
createdByName: "",
|
|
16
|
+
assignee: "",
|
|
17
|
+
location: "",
|
|
18
|
+
attachments: [],
|
|
19
|
+
feedback: "",
|
|
20
|
+
status: "",
|
|
21
|
+
createdAt: "",
|
|
22
|
+
updatedAt: "",
|
|
23
|
+
deletedAt: "",
|
|
24
|
+
highPriority: false,
|
|
25
|
+
block: "",
|
|
26
|
+
level: "",
|
|
27
|
+
unit: "",
|
|
28
|
+
serviceProvider: "",
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
async function getWorkOrders({
|
|
32
|
+
page = 1,
|
|
33
|
+
organization = "",
|
|
34
|
+
site = "",
|
|
35
|
+
status = "to-do",
|
|
36
|
+
search = "",
|
|
37
|
+
limit = 10,
|
|
38
|
+
} = {}) {
|
|
39
|
+
try {
|
|
40
|
+
return useNuxtApp().$api<Record<string, any>>(
|
|
41
|
+
`/api/work-orders/organization/${organization}/site/${site}/status/${status}`,
|
|
42
|
+
{
|
|
43
|
+
method: "GET",
|
|
44
|
+
query: { page, search, limit },
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(err);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createWorkOrder(payload: TWorkOrderCreate) {
|
|
53
|
+
return useNuxtApp().$api<Record<string, any>>("/api/work-orders", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: payload,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
workOrders,
|
|
61
|
+
workOrder,
|
|
62
|
+
page,
|
|
63
|
+
pages,
|
|
64
|
+
pageRange,
|
|
65
|
+
createWorkOrder,
|
|
66
|
+
getWorkOrders,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(async () => {
|
|
2
|
+
const { cookieConfig } = useRuntimeConfig().public;
|
|
3
|
+
|
|
4
|
+
// Get access token from cookies
|
|
5
|
+
const accessToken = useCookie("accessToken", cookieConfig).value;
|
|
6
|
+
|
|
7
|
+
if (!accessToken) {
|
|
8
|
+
// Redirect to login page if no access token
|
|
9
|
+
return navigateTo({ name: "index" });
|
|
10
|
+
}
|
|
11
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const hexSchema = z
|
|
4
|
+
.string()
|
|
5
|
+
.regex(/^[0-9a-fA-F]{24}$/, "Invalid organization ID");
|
|
6
|
+
|
|
7
|
+
export default defineNuxtRouteMiddleware(async (to) => {
|
|
8
|
+
if (import.meta.server) return;
|
|
9
|
+
|
|
10
|
+
const { organization, org } = to.params;
|
|
11
|
+
|
|
12
|
+
if (!hexSchema.safeParse(organization ?? org).success) {
|
|
13
|
+
return navigateTo(
|
|
14
|
+
{ name: "require-organization-membership" },
|
|
15
|
+
{ replace: true }
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const hexSchema = z.string().regex(/^[0-9a-fA-F]{24}$/, "Invalid customer ID");
|
|
4
|
+
|
|
5
|
+
export default defineNuxtRouteMiddleware(async (to, from) => {
|
|
6
|
+
if (import.meta.server) return;
|
|
7
|
+
|
|
8
|
+
const site = to.params.site as string;
|
|
9
|
+
|
|
10
|
+
if (!hexSchema.safeParse(site).success) {
|
|
11
|
+
return navigateTo({ name: "require-customer" }, { replace: true });
|
|
12
|
+
}
|
|
13
|
+
});
|
package/nuxt.config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
|
|
|
3
3
|
export default defineNuxtConfig({
|
|
4
4
|
devtools: { enabled: true },
|
|
5
5
|
build: {
|
|
6
|
-
transpile: ["vuetify"],
|
|
6
|
+
transpile: ["vuetify", "nuxt-signature-pad"],
|
|
7
7
|
},
|
|
8
8
|
|
|
9
9
|
runtimeConfig: {
|
|
@@ -13,6 +13,7 @@ export default defineNuxtConfig({
|
|
|
13
13
|
secure: true,
|
|
14
14
|
maxAge: 30 * 24 * 60 * 60,
|
|
15
15
|
},
|
|
16
|
+
APP: (process.env.APP as string) ?? "App",
|
|
16
17
|
API_DO_STORAGE_ENDPOINT:
|
|
17
18
|
(process.env.API_DO_STORAGE_ENDPOINT as string) ?? "",
|
|
18
19
|
APP_NAME: (process.env.APP_NAME as string) ?? "App",
|
|
@@ -37,6 +38,7 @@ export default defineNuxtConfig({
|
|
|
37
38
|
config.plugins.push(vuetify({ autoImport: true }));
|
|
38
39
|
});
|
|
39
40
|
},
|
|
41
|
+
"nuxt-signature-pad",
|
|
40
42
|
],
|
|
41
43
|
|
|
42
44
|
vite: {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@iservice365/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.1
|
|
5
|
+
"version": "0.2.1",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public"
|
|
@@ -20,14 +20,18 @@
|
|
|
20
20
|
"@nuxt/eslint": "latest",
|
|
21
21
|
"eslint": "^9.14.0",
|
|
22
22
|
"nuxt": "^3.13.2",
|
|
23
|
-
"
|
|
23
|
+
"nuxt-signature-pad": "1.3.0",
|
|
24
|
+
"typescript": "^5.8.3",
|
|
24
25
|
"vite-plugin-vuetify": "^2.0.4",
|
|
25
26
|
"vue": "latest",
|
|
26
27
|
"vuetify": "^3.7.3"
|
|
27
28
|
},
|
|
28
29
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
|
29
30
|
"dependencies": {
|
|
31
|
+
"@iconify/vue": "^5.0.0",
|
|
30
32
|
"@mdi/font": "^7.4.47",
|
|
31
|
-
"sass": "^1.80.6"
|
|
33
|
+
"sass": "^1.80.6",
|
|
34
|
+
"vue3-signature": "^0.2.4",
|
|
35
|
+
"zod": "^3.24.2"
|
|
32
36
|
}
|
|
33
37
|
}
|
package/pages/index.vue
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="fill-height" justify="center" align-content="center">
|
|
3
|
+
<v-card class="pa-4" elevation="4" color="success lighten-1">
|
|
4
|
+
<v-card-title class="text-white d-flex align-center">
|
|
5
|
+
<v-icon class="mr-2">mdi-check-circle</v-icon>
|
|
6
|
+
Successfully Linked Payment Method
|
|
7
|
+
</v-card-title>
|
|
8
|
+
|
|
9
|
+
<v-card-actions class="d-flex justify-center">
|
|
10
|
+
<v-btn
|
|
11
|
+
color="white"
|
|
12
|
+
variant="tonal"
|
|
13
|
+
rounded="xl"
|
|
14
|
+
size="large"
|
|
15
|
+
@click="closeWindow"
|
|
16
|
+
>Close</v-btn
|
|
17
|
+
>
|
|
18
|
+
</v-card-actions>
|
|
19
|
+
</v-card>
|
|
20
|
+
</v-row>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup>
|
|
24
|
+
definePageMeta({
|
|
25
|
+
layout: "plain",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const closeWindow = () => {
|
|
29
|
+
window.close();
|
|
30
|
+
};
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="fill-height" align="center" justify="center">
|
|
3
|
+
<v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
|
|
4
|
+
You must have a customer to view this page. Please join or contact your
|
|
5
|
+
administrator for access.
|
|
6
|
+
<v-row no-gutters justify="center" align="center" class="mt-2">
|
|
7
|
+
<v-btn
|
|
8
|
+
@click="goToAccount"
|
|
9
|
+
rounded="xl"
|
|
10
|
+
variant="tonal"
|
|
11
|
+
class="text-none text-subtitle-2"
|
|
12
|
+
>
|
|
13
|
+
Go to Organization Page
|
|
14
|
+
</v-btn>
|
|
15
|
+
<span class="mx-2">or</span>
|
|
16
|
+
<v-btn
|
|
17
|
+
@click="createCustomer"
|
|
18
|
+
rounded="xl"
|
|
19
|
+
variant="tonal"
|
|
20
|
+
class="text-none text-subtitle-2"
|
|
21
|
+
>
|
|
22
|
+
Create an Customer
|
|
23
|
+
</v-btn>
|
|
24
|
+
</v-row>
|
|
25
|
+
</v-col>
|
|
26
|
+
</v-row>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
definePageMeta({
|
|
31
|
+
layout: "plain",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const { APP_NAME, APP_ORG } = useRuntimeConfig().public;
|
|
35
|
+
|
|
36
|
+
function goToAccount() {
|
|
37
|
+
window.location.href = `${APP_ORG}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { authenticate } = useLocalAuth();
|
|
41
|
+
|
|
42
|
+
authenticate();
|
|
43
|
+
|
|
44
|
+
const { currentUser } = useLocalAuth();
|
|
45
|
+
|
|
46
|
+
function createCustomer() {
|
|
47
|
+
if (APP_NAME.toLowerCase() === "org") {
|
|
48
|
+
navigateTo({
|
|
49
|
+
name: "org-organizations-customers-add",
|
|
50
|
+
params: { organization: currentUser.value?.defaultOrg },
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
window.location.href = `${APP_ORG}/org/${currentUser.value?.defaultOrg}/customers/add`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</script>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="fill-height" align="center" justify="center">
|
|
3
|
+
<v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
|
|
4
|
+
You must be a member of an organization to view this page. Please join or
|
|
5
|
+
contact your administrator for access.
|
|
6
|
+
<v-row no-gutters justify="center" align="center" class="mt-2">
|
|
7
|
+
<v-btn
|
|
8
|
+
@click="goToAccount"
|
|
9
|
+
rounded="xl"
|
|
10
|
+
variant="tonal"
|
|
11
|
+
class="text-none text-subtitle-2"
|
|
12
|
+
>
|
|
13
|
+
Go to Account Page
|
|
14
|
+
</v-btn>
|
|
15
|
+
<span class="mx-2">or</span>
|
|
16
|
+
<v-btn
|
|
17
|
+
@click="createOrg"
|
|
18
|
+
rounded="xl"
|
|
19
|
+
variant="tonal"
|
|
20
|
+
class="text-none text-subtitle-2"
|
|
21
|
+
>
|
|
22
|
+
Create an Organization
|
|
23
|
+
</v-btn>
|
|
24
|
+
</v-row>
|
|
25
|
+
</v-col>
|
|
26
|
+
</v-row>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script setup lang="ts">
|
|
30
|
+
definePageMeta({
|
|
31
|
+
layout: "plain",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const { APP_ACCOUNT, APP_NAME, APP_ORG } = useRuntimeConfig().public;
|
|
35
|
+
|
|
36
|
+
function goToAccount() {
|
|
37
|
+
window.location.href = `${APP_ACCOUNT}/home`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createOrg() {
|
|
41
|
+
if (APP_NAME.toLowerCase() === "org") {
|
|
42
|
+
navigateTo({ name: "organizations-create" });
|
|
43
|
+
} else {
|
|
44
|
+
window.location.href = `${APP_ORG}/organizations/create`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="fill-height" align="center" justify="center">
|
|
3
|
+
<v-col cols="12" lg="6" md="6" sm="6" class="text-center text-subtitle-1">
|
|
4
|
+
You must be a member of an organization to view this page. Please join or
|
|
5
|
+
contact your administrator for access.
|
|
6
|
+
<v-row no-gutters justify="center" class="mt-2">
|
|
7
|
+
<v-btn
|
|
8
|
+
@click="goToAccount"
|
|
9
|
+
rounded="xl"
|
|
10
|
+
variant="tonal"
|
|
11
|
+
class="text-none text-subtitle-2"
|
|
12
|
+
>
|
|
13
|
+
Go to Account Page
|
|
14
|
+
</v-btn>
|
|
15
|
+
</v-row>
|
|
16
|
+
</v-col>
|
|
17
|
+
</v-row>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script setup lang="ts">
|
|
21
|
+
definePageMeta({
|
|
22
|
+
layout: "plain",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function goToAccount() {
|
|
26
|
+
const { APP_ACCOUNT } = useRuntimeConfig().public;
|
|
27
|
+
window.location.href = `${APP_ACCOUNT}/home`;
|
|
28
|
+
}
|
|
29
|
+
</script>
|
package/plugins/API.ts
CHANGED
|
@@ -7,31 +7,8 @@ export default defineNuxtPlugin(() => {
|
|
|
7
7
|
retryStatusCodes: [401],
|
|
8
8
|
retryDelay: 500,
|
|
9
9
|
onRequest({ options }) {
|
|
10
|
-
const
|
|
11
|
-
options.headers
|
|
12
|
-
? { Authorization: `Bearer ${accessToken}` }
|
|
13
|
-
: {};
|
|
14
|
-
},
|
|
15
|
-
|
|
16
|
-
async onResponseError({ response }) {
|
|
17
|
-
if (response.status === 401) {
|
|
18
|
-
await $fetch("/api/auth", {
|
|
19
|
-
baseURL: "/",
|
|
20
|
-
method: "PUT",
|
|
21
|
-
server: false,
|
|
22
|
-
credentials: "include",
|
|
23
|
-
body: JSON.stringify({
|
|
24
|
-
token: useCookie("refreshToken", cookieConfig).value,
|
|
25
|
-
}),
|
|
26
|
-
|
|
27
|
-
onResponse({ response }) {
|
|
28
|
-
if (response.status === 200) {
|
|
29
|
-
useCookie("accessToken", cookieConfig).value =
|
|
30
|
-
response._data.token;
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
}
|
|
10
|
+
const sid = useCookie("sid", cookieConfig).value ?? "";
|
|
11
|
+
options.headers.set("Authorization", sid);
|
|
35
12
|
},
|
|
36
13
|
});
|
|
37
14
|
|