@goweekdays/layer-core 1.0.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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.github/workflows/main.yml +19 -0
- package/.github/workflows/publish.yml +39 -0
- package/CHANGELOG.md +7 -0
- package/README.md +75 -0
- package/app/app.vue +8 -0
- package/app/assets/fonts/ProductSans-Black.ttf +0 -0
- package/app/assets/fonts/ProductSans-BlackItalic.ttf +0 -0
- package/app/assets/fonts/ProductSans-Bold.ttf +0 -0
- package/app/assets/fonts/ProductSans-BoldItalic.ttf +0 -0
- package/app/assets/fonts/ProductSans-Italic.ttf +0 -0
- package/app/assets/fonts/ProductSans-Light.ttf +0 -0
- package/app/assets/fonts/ProductSans-LightItalic.ttf +0 -0
- package/app/assets/fonts/ProductSans-Medium.ttf +0 -0
- package/app/assets/fonts/ProductSans-MediumItalic.ttf +0 -0
- package/app/assets/fonts/ProductSans-Regular.ttf +0 -0
- package/app/assets/fonts/ProductSans-Thin.ttf +0 -0
- package/app/assets/fonts/ProductSans-ThinItalic.ttf +0 -0
- package/app/assets/main.css +10 -0
- package/app/assets/settings.scss +89 -0
- package/app/components/Input/ListGroupSelection.vue +107 -0
- package/app/components/Input/Password.vue +35 -0
- package/app/composables/useLocalAuth.ts +131 -0
- package/app/composables/useLocalSetup.ts +34 -0
- package/app/composables/useMember.ts +100 -0
- package/app/composables/useRole.ts +87 -0
- package/app/composables/useUtils.ts +308 -0
- package/app/layouts/plain.vue +7 -0
- package/app/middleware/01.auth.ts +14 -0
- package/app/middleware/org.ts +13 -0
- package/app/pages/forgot-password.vue +76 -0
- package/app/pages/index.vue +3 -0
- package/app/pages/login.vue +134 -0
- package/app/pages/logout.vue +18 -0
- package/app/pages/privacy-policy.vue +23 -0
- package/app/pages/refund-policy.vue +23 -0
- package/app/pages/sign-up.vue +141 -0
- package/app/pages/terms-and-conditions.vue +23 -0
- package/app/plugins/member.client.ts +66 -0
- package/app/plugins/secure.client.ts +34 -0
- package/app/plugins/vuetify.ts +54 -0
- package/app/public/background.png +0 -0
- package/app/public/favicon.svg +1 -0
- package/app/public/logo.png +0 -0
- package/app/public/pwa-192x192.png +0 -0
- package/app/public/pwa-512x512.png +0 -0
- package/app/public/robots.txt +1 -0
- package/app/types/local.d.ts +113 -0
- package/app/types/member.d.ts +13 -0
- package/app/types/role.d.ts +12 -0
- package/content/privacy-policy.md +151 -0
- package/content/refund-policy.md +65 -0
- package/content/terms-and-conditions.md +137 -0
- package/content.config.ts +10 -0
- package/nuxt.config.ts +45 -0
- package/package.json +32 -0
- package/public/favicon.ico +0 -0
- package/public/robots.txt +2 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export default function useMember() {
|
|
2
|
+
const members = useState<Array<TMember>>("members", () => []);
|
|
3
|
+
const page = useState("page", () => 1);
|
|
4
|
+
const pages = useState("pages", () => 0);
|
|
5
|
+
const pageRange = useState("pageRange", () => "-- - -- of --");
|
|
6
|
+
|
|
7
|
+
const member = useState<TMember>("member", () => ({
|
|
8
|
+
_id: "",
|
|
9
|
+
org: "",
|
|
10
|
+
orgName: "",
|
|
11
|
+
name: "",
|
|
12
|
+
user: "",
|
|
13
|
+
role: "",
|
|
14
|
+
roleName: "",
|
|
15
|
+
status: "",
|
|
16
|
+
createdAt: "",
|
|
17
|
+
updatedAt: "",
|
|
18
|
+
deletedAt: "",
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
function getByUserId(user: string) {
|
|
22
|
+
return $fetch<TMember>(`/api/members/user/${user}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getByUserType(user: string, type: string, org?: string) {
|
|
26
|
+
return $fetch<TMember>(`/api/members/user/${user}/app/${type}`, {
|
|
27
|
+
method: "GET",
|
|
28
|
+
query: { org },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getAll({
|
|
33
|
+
page = 1,
|
|
34
|
+
search = "",
|
|
35
|
+
limit = 10,
|
|
36
|
+
user = "",
|
|
37
|
+
org = "",
|
|
38
|
+
type = "",
|
|
39
|
+
status = "active",
|
|
40
|
+
} = {}) {
|
|
41
|
+
return $fetch<Record<string, any>>("/api/members", {
|
|
42
|
+
method: "GET",
|
|
43
|
+
query: { page, limit, search, user, org, type, status },
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createUserByVerification(
|
|
48
|
+
verificationId: string,
|
|
49
|
+
payload: Record<string, any>
|
|
50
|
+
) {
|
|
51
|
+
return $fetch<Record<string, any>>(`/api/users/invite/${verificationId}`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
body: payload,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function createMemberInvite(verificatonId: string) {
|
|
58
|
+
return $fetch<TMember>(`/api/members/verification/${verificatonId}`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateMemberStatus(id: string, status: string) {
|
|
64
|
+
return $fetch<Record<string, any>>(
|
|
65
|
+
`/api/members/status/${status}/id/${id}`,
|
|
66
|
+
{
|
|
67
|
+
method: "PUT",
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function updateMemberRole(
|
|
73
|
+
id: string,
|
|
74
|
+
role: string,
|
|
75
|
+
type: string,
|
|
76
|
+
org: string
|
|
77
|
+
) {
|
|
78
|
+
return $fetch<Record<string, any>>(
|
|
79
|
+
`/api/members/id/${id}/role/${role}/type/${type}/org/${org}`,
|
|
80
|
+
{
|
|
81
|
+
method: "PUT",
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
members,
|
|
87
|
+
member,
|
|
88
|
+
page,
|
|
89
|
+
pages,
|
|
90
|
+
pageRange,
|
|
91
|
+
|
|
92
|
+
getAll,
|
|
93
|
+
getByUserId,
|
|
94
|
+
createUserByVerification,
|
|
95
|
+
createMemberInvite,
|
|
96
|
+
getByUserType,
|
|
97
|
+
updateMemberStatus,
|
|
98
|
+
updateMemberRole,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export default function useRole() {
|
|
2
|
+
function createRole(
|
|
3
|
+
{ name, permissions, type, org } = {} as {
|
|
4
|
+
name: string;
|
|
5
|
+
permissions: Array<string>;
|
|
6
|
+
type: string;
|
|
7
|
+
org: string;
|
|
8
|
+
}
|
|
9
|
+
) {
|
|
10
|
+
return $fetch("/api/roles", {
|
|
11
|
+
method: "POST",
|
|
12
|
+
body: { name, permissions, type, org },
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getRoles({
|
|
17
|
+
search = "",
|
|
18
|
+
page = 1,
|
|
19
|
+
limit = 20,
|
|
20
|
+
type = "",
|
|
21
|
+
org = "",
|
|
22
|
+
} = {}) {
|
|
23
|
+
return $fetch<Record<string, any>>("/api/roles", {
|
|
24
|
+
method: "GET",
|
|
25
|
+
query: { search, page, limit, type, org },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getRoleById(id: string) {
|
|
30
|
+
return $fetch<Record<string, any>>(`/api/roles/id/${id}`, {
|
|
31
|
+
method: "GET",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function updateRoleById(
|
|
36
|
+
_id: string,
|
|
37
|
+
name?: string,
|
|
38
|
+
permissions?: Array<string>
|
|
39
|
+
) {
|
|
40
|
+
return $fetch(`/api/roles/id/${_id}`, {
|
|
41
|
+
method: "PATCH",
|
|
42
|
+
body: { name, permissions },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function updatePermissionById(_id: string, permissions?: Array<string>) {
|
|
47
|
+
return $fetch(`/api/roles/permissions/id/${_id}`, {
|
|
48
|
+
method: "PATCH",
|
|
49
|
+
body: { permissions },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function updateRoleFieldById(_id: string, field: string, value: string) {
|
|
54
|
+
return $fetch(`/api/roles/${_id}`, {
|
|
55
|
+
method: "PATCH",
|
|
56
|
+
body: { field, value },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function deleteRole(_id: string) {
|
|
61
|
+
return $fetch<Record<string, any>>(`/api/roles/${_id}`, {
|
|
62
|
+
method: "DELETE",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const role = ref({
|
|
67
|
+
_id: "",
|
|
68
|
+
name: "",
|
|
69
|
+
org: "",
|
|
70
|
+
permissions: [],
|
|
71
|
+
createdAt: "",
|
|
72
|
+
updatedAt: "",
|
|
73
|
+
deletedAt: "",
|
|
74
|
+
default: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
role,
|
|
79
|
+
createRole,
|
|
80
|
+
getRoles,
|
|
81
|
+
getRoleById,
|
|
82
|
+
updateRoleById,
|
|
83
|
+
updateRoleFieldById,
|
|
84
|
+
deleteRole,
|
|
85
|
+
updatePermissionById,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
export default function useUtils() {
|
|
2
|
+
const requiredRule = (v: string) => !!v || "Required";
|
|
3
|
+
|
|
4
|
+
const emailRule = (v: string): true | string => {
|
|
5
|
+
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6
|
+
if (!v) return true;
|
|
7
|
+
return regex.test(v) || "Please enter a valid email address";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const validateDate = (value: string): boolean | string => {
|
|
11
|
+
const dateRegex = /^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/;
|
|
12
|
+
|
|
13
|
+
if (!dateRegex.test(value)) return "Invalid date format (MM/DD/YYYY)";
|
|
14
|
+
|
|
15
|
+
return true;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function requireListRule(value: string[]) {
|
|
19
|
+
return value.length ? "" : "Required";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const minOneMonthAdvance = (value: string): boolean | string => {
|
|
23
|
+
if (!/^(0[1-9]|1[0-2])\/(0[1-9]|[12][0-9]|3[01])\/\d{4}$/.test(value)) {
|
|
24
|
+
return "Invalid date format (MM/DD/YYYY)";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const [month, day, year] = value.split("/").map(Number);
|
|
28
|
+
const selectedDate = new Date(year ?? 0, (month ?? 0) - 1, day);
|
|
29
|
+
|
|
30
|
+
const currentDate = new Date();
|
|
31
|
+
const minDate = new Date();
|
|
32
|
+
minDate.setMonth(currentDate.getMonth() + 1); // 1 month in advance
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
selectedDate >= minDate || "Date must be at least 1 month in advance"
|
|
36
|
+
);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const passwordRule = (v: string) =>
|
|
40
|
+
v.length >= 8 || "Password must be at least 8 characters long";
|
|
41
|
+
|
|
42
|
+
const confirmPasswordRule =
|
|
43
|
+
(newPassword: Ref<string>, confirmPassword: Ref<string>) => () =>
|
|
44
|
+
confirmPassword.value === newPassword.value || "Passwords must match";
|
|
45
|
+
|
|
46
|
+
const validateEmail = (email: Ref<string>, emailErrors: Ref<string[]>) => {
|
|
47
|
+
emailErrors.value = [];
|
|
48
|
+
|
|
49
|
+
if (!email.value) {
|
|
50
|
+
emailErrors.value.push("Please input your email");
|
|
51
|
+
} else {
|
|
52
|
+
const result = emailRule(email.value);
|
|
53
|
+
if (result !== true) {
|
|
54
|
+
emailErrors.value.push(result);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const validateWord = (value: string): boolean | string => {
|
|
60
|
+
if (value.trim().length < 3) return "Must be at least 3 characters";
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const validatePassword = (
|
|
65
|
+
password: Ref<string>,
|
|
66
|
+
passwordErrors: Ref<string[]>
|
|
67
|
+
) => {
|
|
68
|
+
passwordErrors.value = [];
|
|
69
|
+
|
|
70
|
+
if (!password.value) {
|
|
71
|
+
passwordErrors.value.push("Please input your password");
|
|
72
|
+
} else if (!passwordRule(password.value)) {
|
|
73
|
+
passwordErrors.value.push("Password must be at least 8 characters long");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function validateKey(value: string) {
|
|
78
|
+
const pattern = /^[a-z._-]+$/;
|
|
79
|
+
return (
|
|
80
|
+
pattern.test(value) || "Key must be lowercase and can include ., _, -"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function back() {
|
|
85
|
+
useRouter().back();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function debounce<T extends (...args: any[]) => void>(
|
|
89
|
+
func: T,
|
|
90
|
+
wait: number
|
|
91
|
+
): (...args: Parameters<T>) => void {
|
|
92
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
93
|
+
|
|
94
|
+
return function (...args: Parameters<T>) {
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
timeoutId = setTimeout(() => func(...args), wait);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function insertBetween(arr: any, elementToInsert: any) {
|
|
101
|
+
return arr.flatMap((item: any, index: number, array: any) =>
|
|
102
|
+
index < array.length - 1 ? [item, elementToInsert] : [item]
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getNameInitials(name: string) {
|
|
107
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
108
|
+
return ""; // Default fallback for invalid input
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const words = name.trim().split(/\s+/); // Split by spaces
|
|
112
|
+
|
|
113
|
+
if (words.length === 1) {
|
|
114
|
+
return words[0]?.slice(0, 2).toUpperCase(); // Take first two letters of the single word
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return words
|
|
118
|
+
.map((word) => word[0]?.toUpperCase()) // Take the first letter of each word and capitalize
|
|
119
|
+
.join("")
|
|
120
|
+
.slice(0, 2); // Limit to 2 characters
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getDimensions(
|
|
124
|
+
file: File
|
|
125
|
+
): Promise<{ width: number; height: number }> {
|
|
126
|
+
const img = new Image();
|
|
127
|
+
img.src = URL.createObjectURL(file);
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
img.onload = () => {
|
|
130
|
+
const width = img.width;
|
|
131
|
+
const height = img.height;
|
|
132
|
+
resolve({ width, height });
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
img.onerror = () => {
|
|
136
|
+
reject(new Error("Failed to read image dimensions"));
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function getCountries() {
|
|
142
|
+
try {
|
|
143
|
+
const countries = await $fetch<Array<Record<string, any>>>(
|
|
144
|
+
"https://restcountries.com/v3.1/all?fields=name,currencies,idd",
|
|
145
|
+
{ method: "GET" }
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const uniqueCountries: Map<
|
|
149
|
+
string,
|
|
150
|
+
{ index: string; name: string; code: string }
|
|
151
|
+
> = new Map();
|
|
152
|
+
|
|
153
|
+
countries.forEach((country) => {
|
|
154
|
+
let suffixes = country.idd?.suffixes?.[0] || "";
|
|
155
|
+
let code = `${country.idd?.root || ""}${suffixes}`;
|
|
156
|
+
let name = country.name?.common || "";
|
|
157
|
+
|
|
158
|
+
if (name && code && !uniqueCountries.has(code)) {
|
|
159
|
+
uniqueCountries.set(code, { index: `${name}-${code}`, name, code });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return Array.from(uniqueCountries.values()).sort((a, b) =>
|
|
164
|
+
a.name.localeCompare(b.name)
|
|
165
|
+
);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(error);
|
|
168
|
+
throw new Error("Failed to fetch countries.");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const formatter = new Intl.NumberFormat("en-US", {
|
|
173
|
+
minimumFractionDigits: 2,
|
|
174
|
+
maximumFractionDigits: 2,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
function formatNumber(
|
|
178
|
+
amount: number,
|
|
179
|
+
options: {
|
|
180
|
+
currency?: string;
|
|
181
|
+
locale?: string;
|
|
182
|
+
useSymbol?: boolean;
|
|
183
|
+
decimalPlaces?: number;
|
|
184
|
+
} = {}
|
|
185
|
+
): string {
|
|
186
|
+
const {
|
|
187
|
+
currency,
|
|
188
|
+
locale = "en-US",
|
|
189
|
+
useSymbol = false,
|
|
190
|
+
decimalPlaces = 2,
|
|
191
|
+
} = options;
|
|
192
|
+
|
|
193
|
+
return new Intl.NumberFormat(locale, {
|
|
194
|
+
style: useSymbol && currency ? "currency" : "decimal",
|
|
195
|
+
currency: currency || "USD", // Default currency (ignored if `useSymbol` is false)
|
|
196
|
+
minimumFractionDigits: decimalPlaces,
|
|
197
|
+
maximumFractionDigits: decimalPlaces,
|
|
198
|
+
}).format(amount);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 🔹 Examples:
|
|
202
|
+
// console.log(formatNumber(1234.56)); // "1,234.56" (comma separator, 2 decimals)
|
|
203
|
+
// console.log(formatNumber(1234.56, { decimalPlaces: 0 })); // "1,234" (no decimals)
|
|
204
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "USD" })); // "$1,234.56"
|
|
205
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "PHP", decimalPlaces: 0 })); // "₱1,234"
|
|
206
|
+
// console.log(formatNumber(1234.56, { useSymbol: false, decimalPlaces: 0 })); // "1,234"
|
|
207
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE" })); // "1.234,56 €"
|
|
208
|
+
// console.log(formatNumber(1234.56, { useSymbol: true, currency: "EUR", locale: "de-DE", decimalPlaces: 0 })); // "1.234 €"
|
|
209
|
+
|
|
210
|
+
function computeTieredCost(
|
|
211
|
+
seats: number,
|
|
212
|
+
tiers: { min: number; max: number; price: number }[],
|
|
213
|
+
remainingDays?: number,
|
|
214
|
+
totalDaysInMonth?: number
|
|
215
|
+
) {
|
|
216
|
+
let totalCost = 0;
|
|
217
|
+
|
|
218
|
+
for (let i = 1; i <= seats; i++) {
|
|
219
|
+
for (const { min, max, price } of tiers) {
|
|
220
|
+
const effectiveMax = max === 0 ? Infinity : max;
|
|
221
|
+
|
|
222
|
+
if (i >= min && i <= effectiveMax) {
|
|
223
|
+
// Prorate the cost for this seat based on its tier price
|
|
224
|
+
if (remainingDays && totalDaysInMonth) {
|
|
225
|
+
totalCost += price * (remainingDays / totalDaysInMonth);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
totalCost += price;
|
|
230
|
+
break; // Stop checking once we apply the correct tier pricing
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return totalCost; // This now returns the total prorated cost
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function convertPermissionsToArray(permissions: TPermissions) {
|
|
239
|
+
return Object.entries(permissions).flatMap(([resource, actions]) =>
|
|
240
|
+
Object.keys(actions).map((action) => `${resource}:${action}`)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function extractMonthYear(expiry: string) {
|
|
245
|
+
const [month, year] = expiry.split("/");
|
|
246
|
+
return {
|
|
247
|
+
month: month?.padStart(2, "0"),
|
|
248
|
+
year: 2000 + parseInt(year ?? "", 10),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function getRouteParam(field = "") {
|
|
253
|
+
if (!field) return "";
|
|
254
|
+
|
|
255
|
+
return (useRoute().params[field] as string) ?? "";
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function replaceMatch(str: string, match: string, replace: string) {
|
|
259
|
+
return str.replace(new RegExp(match, "g"), replace);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function setRouteParams(...args: Array<Record<string, string>>) {
|
|
263
|
+
const params: Record<string, string> = {};
|
|
264
|
+
|
|
265
|
+
args.forEach((arg) => {
|
|
266
|
+
Object.entries(arg).forEach(([key, value]) => {
|
|
267
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
268
|
+
params[key] = value;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
return params;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function positiveNumberRule(v: number) {
|
|
277
|
+
return (v && v >= 1) || "Value must be greater or equal to 1";
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
requiredRule,
|
|
282
|
+
emailRule,
|
|
283
|
+
passwordRule,
|
|
284
|
+
confirmPasswordRule,
|
|
285
|
+
validateEmail,
|
|
286
|
+
validatePassword,
|
|
287
|
+
back,
|
|
288
|
+
debounce,
|
|
289
|
+
insertBetween,
|
|
290
|
+
getNameInitials,
|
|
291
|
+
getDimensions,
|
|
292
|
+
validateWord,
|
|
293
|
+
getCountries,
|
|
294
|
+
formatter,
|
|
295
|
+
formatNumber,
|
|
296
|
+
validateDate,
|
|
297
|
+
minOneMonthAdvance,
|
|
298
|
+
computeTieredCost,
|
|
299
|
+
requireListRule,
|
|
300
|
+
convertPermissionsToArray,
|
|
301
|
+
extractMonthYear,
|
|
302
|
+
getRouteParam,
|
|
303
|
+
replaceMatch,
|
|
304
|
+
setRouteParams,
|
|
305
|
+
positiveNumberRule,
|
|
306
|
+
validateKey,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default defineNuxtRouteMiddleware(async () => {
|
|
2
|
+
// Ensure middleware runs only on the client side
|
|
3
|
+
if (import.meta.server) return;
|
|
4
|
+
|
|
5
|
+
const { cookieConfig } = useLocalSetup();
|
|
6
|
+
|
|
7
|
+
// Get access token from cookies
|
|
8
|
+
const sid = useCookie("sid", cookieConfig).value;
|
|
9
|
+
|
|
10
|
+
if (!sid) {
|
|
11
|
+
// Redirect to login page if no access token
|
|
12
|
+
return navigateTo({ name: "index" });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
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((to) => {
|
|
8
|
+
const org = (to.params.org as string) ?? "";
|
|
9
|
+
|
|
10
|
+
if (org && !hexSchema.safeParse(org).success) {
|
|
11
|
+
return navigateTo({ name: "require-organization" }, { replace: true });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters class="fill-height" justify="center" align-content="center">
|
|
3
|
+
<v-col cols="12" class="mb-6">
|
|
4
|
+
<v-row no-gutters justify="center">
|
|
5
|
+
<nuxt-link
|
|
6
|
+
class="text-h2 font-weight-bold text-decoration-none"
|
|
7
|
+
style="color: unset"
|
|
8
|
+
:to="{ name: 'index' }"
|
|
9
|
+
>
|
|
10
|
+
GoWeekdays
|
|
11
|
+
</nuxt-link>
|
|
12
|
+
</v-row>
|
|
13
|
+
</v-col>
|
|
14
|
+
|
|
15
|
+
<v-col cols="12" lg="3" md="4" sm="6">
|
|
16
|
+
<v-form v-model="isValid" @submit.prevent="submit(email)">
|
|
17
|
+
<v-row no-gutters>
|
|
18
|
+
<v-col cols="12">
|
|
19
|
+
<v-row no-gutters>
|
|
20
|
+
<v-col cols="12" class="text-h6 font-weight-bold"> Email </v-col>
|
|
21
|
+
<v-col cols="12">
|
|
22
|
+
<v-text-field
|
|
23
|
+
v-model="email"
|
|
24
|
+
:rules="[requiredRule, emailRule]"
|
|
25
|
+
></v-text-field>
|
|
26
|
+
</v-col>
|
|
27
|
+
</v-row>
|
|
28
|
+
</v-col>
|
|
29
|
+
|
|
30
|
+
<v-col v-if="message" cols="12" class="my-2 text-center font-italic">
|
|
31
|
+
{{ message }}
|
|
32
|
+
</v-col>
|
|
33
|
+
|
|
34
|
+
<v-col cols="12">
|
|
35
|
+
<v-row no-gutters justify="center">
|
|
36
|
+
<v-btn
|
|
37
|
+
type="submit"
|
|
38
|
+
variant="tonal"
|
|
39
|
+
:disabled="!isValid"
|
|
40
|
+
rounded="xl"
|
|
41
|
+
size="large"
|
|
42
|
+
>
|
|
43
|
+
submit
|
|
44
|
+
</v-btn>
|
|
45
|
+
</v-row>
|
|
46
|
+
</v-col>
|
|
47
|
+
|
|
48
|
+
<v-col cols="12" class="mt-2 text-center">
|
|
49
|
+
All good? <nuxt-link :to="{ name: 'login' }">Login</nuxt-link>
|
|
50
|
+
</v-col>
|
|
51
|
+
</v-row>
|
|
52
|
+
</v-form>
|
|
53
|
+
</v-col>
|
|
54
|
+
</v-row>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
definePageMeta({
|
|
59
|
+
layout: "plain",
|
|
60
|
+
});
|
|
61
|
+
const { requiredRule, emailRule } = useUtils();
|
|
62
|
+
const email = ref("");
|
|
63
|
+
const isValid = ref(false);
|
|
64
|
+
|
|
65
|
+
const { forgotPassword } = useLocalAuth();
|
|
66
|
+
const message = ref("");
|
|
67
|
+
|
|
68
|
+
async function submit(email = "") {
|
|
69
|
+
try {
|
|
70
|
+
const result: any = await forgotPassword(email);
|
|
71
|
+
message.value = result.message;
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
message.value = error.message;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
</script>
|