@koloseum/utils 0.1.4 → 0.1.6
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/dist/utils.d.ts +7 -1
- package/dist/utils.js +59 -7
- package/package.json +5 -2
- package/src/utils.test.ts +8 -8
- package/src/utils.ts +75 -11
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from "@koloseum/types/database";
|
|
2
|
-
import type { CustomError, SocialMediaPlatform } from "@koloseum/types/public/auth";
|
|
2
|
+
import type { CustomError, PronounsCheckboxes, SocialMediaPlatform } from "@koloseum/types/public/auth";
|
|
3
3
|
import type { PostgrestError, SupabaseClient } from "@supabase/supabase-js";
|
|
4
4
|
import type { MobilePhoneLocale } from "validator";
|
|
5
5
|
export declare const Status: {
|
|
@@ -66,6 +66,12 @@ export declare const Utility: {
|
|
|
66
66
|
* @returns The age in years, or `NaN` if the input is invalid
|
|
67
67
|
*/
|
|
68
68
|
getAge: (dateOfBirth: string) => number;
|
|
69
|
+
/**
|
|
70
|
+
* Handles the click event for pronouns checkboxes.
|
|
71
|
+
* @param {MouseEvent} e - The click event
|
|
72
|
+
* @param {PronounsCheckboxes} checkboxes - The pronouns checkboxes
|
|
73
|
+
*/
|
|
74
|
+
handlePronounsCheckboxClick: (e: MouseEvent, checkboxes: PronounsCheckboxes) => void;
|
|
69
75
|
/**
|
|
70
76
|
* A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
|
|
71
77
|
* - 9 characters long
|
package/dist/utils.js
CHANGED
|
@@ -18,7 +18,7 @@ export const Status = {
|
|
|
18
18
|
/**
|
|
19
19
|
* A generic password reset request message.
|
|
20
20
|
*/
|
|
21
|
-
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
21
|
+
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
22
22
|
};
|
|
23
23
|
/* Utility functions */
|
|
24
24
|
export const Utility = {
|
|
@@ -59,7 +59,7 @@ export const Utility = {
|
|
|
59
59
|
* @param {string} message - The error message
|
|
60
60
|
* @returns An object with the error `code` and `message`
|
|
61
61
|
*/
|
|
62
|
-
customError: (code, message) =>
|
|
62
|
+
customError: (code, message) => !code || code < 400 || code > 599 ? { code: 500, message: Status.ERROR } : { code, message },
|
|
63
63
|
/**
|
|
64
64
|
* A regular expression for E.164 phone numbers.
|
|
65
65
|
*
|
|
@@ -79,7 +79,7 @@ export const Utility = {
|
|
|
79
79
|
const formattedDate = new Intl.DateTimeFormat(forClient ? "fr-CA" : "en-KE", {
|
|
80
80
|
day: "2-digit",
|
|
81
81
|
month: "2-digit",
|
|
82
|
-
year: "numeric"
|
|
82
|
+
year: "numeric"
|
|
83
83
|
// @ts-ignore: .fromString method exists but is not typed in `any-date-parser` package
|
|
84
84
|
}).format(parser.fromString(date));
|
|
85
85
|
return formattedDate;
|
|
@@ -120,6 +120,56 @@ export const Utility = {
|
|
|
120
120
|
const monthDiff = currentDate.getMonth() - birthDate.getMonth();
|
|
121
121
|
return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
|
|
122
122
|
},
|
|
123
|
+
/**
|
|
124
|
+
* Handles the click event for pronouns checkboxes.
|
|
125
|
+
* @param {MouseEvent} e - The click event
|
|
126
|
+
* @param {PronounsCheckboxes} checkboxes - The pronouns checkboxes
|
|
127
|
+
*/
|
|
128
|
+
handlePronounsCheckboxClick: (e, checkboxes) => {
|
|
129
|
+
const target = e.target;
|
|
130
|
+
if (target.value === "none") {
|
|
131
|
+
for (let checkbox of Object.values(checkboxes))
|
|
132
|
+
if (checkbox && checkbox !== target && checkbox.checked) {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (let checkbox of Object.values(checkboxes))
|
|
137
|
+
if (checkbox && checkbox !== target) {
|
|
138
|
+
checkbox.checked = false;
|
|
139
|
+
checkbox.disabled = !checkbox.disabled;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
if (checkboxes.none?.checked) {
|
|
144
|
+
e.preventDefault();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (target.value === "male" || target.value === "female") {
|
|
148
|
+
const oppositeIndex = target.value === "male" ? "female" : "male";
|
|
149
|
+
const oppositeCheckbox = checkboxes[oppositeIndex];
|
|
150
|
+
if (oppositeCheckbox?.checked) {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (oppositeCheckbox) {
|
|
155
|
+
oppositeCheckbox.checked = false;
|
|
156
|
+
oppositeCheckbox.disabled = !oppositeCheckbox.disabled;
|
|
157
|
+
}
|
|
158
|
+
if (checkboxes.none) {
|
|
159
|
+
checkboxes.none.checked = false;
|
|
160
|
+
if (!checkboxes.neutral?.checked)
|
|
161
|
+
checkboxes.none.disabled = !checkboxes.none.disabled;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (checkboxes.none) {
|
|
166
|
+
checkboxes.none.checked = false;
|
|
167
|
+
if (checkboxes.neutral && !checkboxes.male?.checked && !checkboxes.female?.checked)
|
|
168
|
+
checkboxes.none.disabled = checkboxes.neutral.checked;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
},
|
|
123
173
|
/**
|
|
124
174
|
* A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
|
|
125
175
|
* - 9 characters long
|
|
@@ -148,7 +198,7 @@ export const Utility = {
|
|
|
148
198
|
* @param {CustomError} error - The error object
|
|
149
199
|
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
150
200
|
*/
|
|
151
|
-
parseLoadError: (error) =>
|
|
201
|
+
parseLoadError: (error) => error.code === 500 ? new Error(error.message) : svelteError(error.code, { message: error.message }),
|
|
152
202
|
/**
|
|
153
203
|
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
154
204
|
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
@@ -195,7 +245,7 @@ export const Utility = {
|
|
|
195
245
|
{ code: "42883", status: 404 },
|
|
196
246
|
{ code: "42P01", status: 404 },
|
|
197
247
|
{ code: "42P17", status: 500 },
|
|
198
|
-
{ code: "42501", status: 403 }
|
|
248
|
+
{ code: "42501", status: 403 }
|
|
199
249
|
];
|
|
200
250
|
// Return custom error if Postgrest error code is found
|
|
201
251
|
for (const { code, status } of errorMap)
|
|
@@ -227,7 +277,7 @@ export const Utility = {
|
|
|
227
277
|
* @param {string} input - The input to be sanitised
|
|
228
278
|
* @returns A sanitised string, or an empty string if the input is invalid
|
|
229
279
|
*/
|
|
230
|
-
sanitiseHtml: (input) =>
|
|
280
|
+
sanitiseHtml: (input) => typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} }),
|
|
231
281
|
/**
|
|
232
282
|
* A regular expression for social media handles, without a leading slash or @ character.
|
|
233
283
|
*
|
|
@@ -246,5 +296,7 @@ export const Utility = {
|
|
|
246
296
|
* @param {string} handle - The social media handle to be validated
|
|
247
297
|
* @returns `true` if the handle is valid; `false` otherwise
|
|
248
298
|
*/
|
|
249
|
-
validateSocialMediaHandle: (handle) => !isURL(handle, { require_protocol: false }) &&
|
|
299
|
+
validateSocialMediaHandle: (handle) => !isURL(handle, { require_protocol: false }) &&
|
|
300
|
+
!handle.startsWith("@") &&
|
|
301
|
+
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
250
302
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"./dist/**/*"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "npx tsc",
|
|
27
|
+
"build": "rm -rf dist && npx tsc",
|
|
28
|
+
"check": "npx tsc --noEmit && prettier --check .",
|
|
29
|
+
"format": "prettier --write .",
|
|
28
30
|
"test": "vitest --run"
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
"@playwright/test": "^1.50.1",
|
|
40
42
|
"@types/sanitize-html": "^2.13.0",
|
|
41
43
|
"@types/validator": "^13.12.2",
|
|
44
|
+
"prettier": "^3.5.1",
|
|
42
45
|
"typescript": "^5.0.0",
|
|
43
46
|
"vitest": "^3.0.5"
|
|
44
47
|
}
|
package/src/utils.test.ts
CHANGED
|
@@ -28,7 +28,7 @@ describe("Utility helper", () => {
|
|
|
28
28
|
parsePostgrestError,
|
|
29
29
|
sanitiseHtml,
|
|
30
30
|
validateE164,
|
|
31
|
-
validateSocialMediaHandle
|
|
31
|
+
validateSocialMediaHandle
|
|
32
32
|
} = Utility;
|
|
33
33
|
|
|
34
34
|
// Test each helper
|
|
@@ -103,7 +103,7 @@ describe("Utility helper", () => {
|
|
|
103
103
|
name: "insufficient_privilege",
|
|
104
104
|
message: 'Insufficient privileges: permission denied for relation "restricted_table"',
|
|
105
105
|
details: 'User does not have the necessary permissions to access the relation "restricted_table".',
|
|
106
|
-
hint: ""
|
|
106
|
+
hint: ""
|
|
107
107
|
})
|
|
108
108
|
).toEqual({ error: { code: 403, message: Status.ERROR } });
|
|
109
109
|
expect(
|
|
@@ -112,7 +112,7 @@ describe("Utility helper", () => {
|
|
|
112
112
|
name: "undefined_function",
|
|
113
113
|
message: "Undefined function: function non_existent_function() does not exist",
|
|
114
114
|
details: "Function non_existent_function() does not exist.",
|
|
115
|
-
hint: "No function matches the given name and argument types. You might need to add explicit type casts."
|
|
115
|
+
hint: "No function matches the given name and argument types. You might need to add explicit type casts."
|
|
116
116
|
})
|
|
117
117
|
).toEqual({ error: { code: 404, message: Status.ERROR } });
|
|
118
118
|
expect(
|
|
@@ -121,7 +121,7 @@ describe("Utility helper", () => {
|
|
|
121
121
|
name: "read_only_sql_transaction",
|
|
122
122
|
message: "Read only SQL transaction: cannot execute INSERT in a read-only transaction",
|
|
123
123
|
details: "Cannot execute INSERT in a read-only transaction.",
|
|
124
|
-
hint: ""
|
|
124
|
+
hint: ""
|
|
125
125
|
})
|
|
126
126
|
).toEqual({ error: { code: 405, message: Status.ERROR } });
|
|
127
127
|
expect(
|
|
@@ -130,7 +130,7 @@ describe("Utility helper", () => {
|
|
|
130
130
|
name: "unique_violation",
|
|
131
131
|
message: 'Uniqueness violation: duplicate key value violates unique constraint "users_username_key"',
|
|
132
132
|
details: "Key (username)=(john_doe) already exists.",
|
|
133
|
-
hint: ""
|
|
133
|
+
hint: ""
|
|
134
134
|
})
|
|
135
135
|
).toEqual({ error: { code: 409, message: Status.ERROR } });
|
|
136
136
|
expect(
|
|
@@ -139,7 +139,7 @@ describe("Utility helper", () => {
|
|
|
139
139
|
name: "configuration_file_error",
|
|
140
140
|
message: "invalid page header in block 0 of relation base/12345/67890",
|
|
141
141
|
details: "The page header is corrupted. This may indicate hardware or file system issues.",
|
|
142
|
-
hint: ""
|
|
142
|
+
hint: ""
|
|
143
143
|
})
|
|
144
144
|
).toEqual({ error: { code: 500, message: Status.ERROR } });
|
|
145
145
|
expect(
|
|
@@ -148,7 +148,7 @@ describe("Utility helper", () => {
|
|
|
148
148
|
name: "connection_does_not_exist",
|
|
149
149
|
message: "connection does not exist",
|
|
150
150
|
details: "The connection to the database was lost or never established.",
|
|
151
|
-
hint: ""
|
|
151
|
+
hint: ""
|
|
152
152
|
})
|
|
153
153
|
).toEqual({ error: { code: 503, message: Status.ERROR } });
|
|
154
154
|
expect(
|
|
@@ -157,7 +157,7 @@ describe("Utility helper", () => {
|
|
|
157
157
|
name: "raise_exception",
|
|
158
158
|
message: "I'm a teapot!",
|
|
159
159
|
details: "Pretty simple",
|
|
160
|
-
hint: "418"
|
|
160
|
+
hint: "418"
|
|
161
161
|
})
|
|
162
162
|
).toEqual({ error: { code: 418, message: "I'm a teapot!" } });
|
|
163
163
|
});
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from "@koloseum/types/database";
|
|
2
|
-
import type { CustomError, SocialMediaPlatform } from "@koloseum/types/public/auth";
|
|
2
|
+
import type { CustomError, PronounsCheckboxes, SocialMediaPlatform } from "@koloseum/types/public/auth";
|
|
3
3
|
|
|
4
4
|
import type { PostgrestError, SupabaseClient } from "@supabase/supabase-js";
|
|
5
5
|
import type { MobilePhoneLocale } from "validator";
|
|
@@ -27,7 +27,7 @@ export const Status = {
|
|
|
27
27
|
/**
|
|
28
28
|
* A generic password reset request message.
|
|
29
29
|
*/
|
|
30
|
-
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
30
|
+
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/* Utility functions */
|
|
@@ -39,12 +39,16 @@ export const Utility = {
|
|
|
39
39
|
* @param {"GET" | "POST"} [method="GET"] - The HTTP method to use
|
|
40
40
|
* @returns The response from the Edge function
|
|
41
41
|
*/
|
|
42
|
-
callEdgeFunction: async (
|
|
42
|
+
callEdgeFunction: async (
|
|
43
|
+
supabase: SupabaseClient<Database>,
|
|
44
|
+
path: string,
|
|
45
|
+
method: "GET" | "POST" = "GET"
|
|
46
|
+
): Promise<{ data?: any; error?: string }> => {
|
|
43
47
|
// Fetch response
|
|
44
48
|
const { data, error } = await supabase.functions.invoke(path, { method });
|
|
45
49
|
|
|
46
50
|
// Return error if any
|
|
47
|
-
if (error instanceof FunctionsHttpError) return { error: await error.context.json() };
|
|
51
|
+
if (error instanceof FunctionsHttpError) return { error: (await error.context.json()).message };
|
|
48
52
|
else if (error instanceof FunctionsRelayError) return { error: error.message };
|
|
49
53
|
else if (error instanceof FunctionsFetchError) return { error: error.message };
|
|
50
54
|
|
|
@@ -69,7 +73,8 @@ export const Utility = {
|
|
|
69
73
|
* @param {string} message - The error message
|
|
70
74
|
* @returns An object with the error `code` and `message`
|
|
71
75
|
*/
|
|
72
|
-
customError: (code: number | undefined, message: string): CustomError =>
|
|
76
|
+
customError: (code: number | undefined, message: string): CustomError =>
|
|
77
|
+
!code || code < 400 || code > 599 ? { code: 500, message: Status.ERROR } : { code, message },
|
|
73
78
|
/**
|
|
74
79
|
* A regular expression for E.164 phone numbers.
|
|
75
80
|
*
|
|
@@ -88,7 +93,7 @@ export const Utility = {
|
|
|
88
93
|
const formattedDate = new Intl.DateTimeFormat(forClient ? "fr-CA" : "en-KE", {
|
|
89
94
|
day: "2-digit",
|
|
90
95
|
month: "2-digit",
|
|
91
|
-
year: "numeric"
|
|
96
|
+
year: "numeric"
|
|
92
97
|
// @ts-ignore: .fromString method exists but is not typed in `any-date-parser` package
|
|
93
98
|
}).format(parser.fromString(date));
|
|
94
99
|
|
|
@@ -129,6 +134,59 @@ export const Utility = {
|
|
|
129
134
|
|
|
130
135
|
return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
|
|
131
136
|
},
|
|
137
|
+
/**
|
|
138
|
+
* Handles the click event for pronouns checkboxes.
|
|
139
|
+
* @param {MouseEvent} e - The click event
|
|
140
|
+
* @param {PronounsCheckboxes} checkboxes - The pronouns checkboxes
|
|
141
|
+
*/
|
|
142
|
+
handlePronounsCheckboxClick: (e: MouseEvent, checkboxes: PronounsCheckboxes) => {
|
|
143
|
+
const target = e.target as EventTarget & HTMLInputElement;
|
|
144
|
+
|
|
145
|
+
if (target.value === "none") {
|
|
146
|
+
for (let checkbox of Object.values(checkboxes))
|
|
147
|
+
if (checkbox && checkbox !== target && checkbox.checked) {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (let checkbox of Object.values(checkboxes))
|
|
153
|
+
if (checkbox && checkbox !== target) {
|
|
154
|
+
checkbox.checked = false;
|
|
155
|
+
checkbox.disabled = !checkbox.disabled;
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
if (checkboxes.none?.checked) {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (target.value === "male" || target.value === "female") {
|
|
164
|
+
const oppositeIndex = target.value === "male" ? "female" : "male";
|
|
165
|
+
const oppositeCheckbox = checkboxes[oppositeIndex];
|
|
166
|
+
|
|
167
|
+
if (oppositeCheckbox?.checked) {
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (oppositeCheckbox) {
|
|
173
|
+
oppositeCheckbox.checked = false;
|
|
174
|
+
oppositeCheckbox.disabled = !oppositeCheckbox.disabled;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (checkboxes.none) {
|
|
178
|
+
checkboxes.none.checked = false;
|
|
179
|
+
if (!checkboxes.neutral?.checked) checkboxes.none.disabled = !checkboxes.none.disabled;
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
if (checkboxes.none) {
|
|
183
|
+
checkboxes.none.checked = false;
|
|
184
|
+
if (checkboxes.neutral && !checkboxes.male?.checked && !checkboxes.female?.checked)
|
|
185
|
+
checkboxes.none.disabled = checkboxes.neutral.checked;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
132
190
|
/**
|
|
133
191
|
* A regular expression for Koloseum Lounge Branch IDs. It covers the following rules:
|
|
134
192
|
* - 9 characters long
|
|
@@ -159,7 +217,8 @@ export const Utility = {
|
|
|
159
217
|
* @param {CustomError} error - The error object
|
|
160
218
|
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
161
219
|
*/
|
|
162
|
-
parseLoadError: (error: CustomError) =>
|
|
220
|
+
parseLoadError: (error: CustomError) =>
|
|
221
|
+
error.code === 500 ? new Error(error.message) : svelteError(error.code, { message: error.message }),
|
|
163
222
|
/**
|
|
164
223
|
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
165
224
|
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
@@ -207,7 +266,7 @@ export const Utility = {
|
|
|
207
266
|
{ code: "42883", status: 404 },
|
|
208
267
|
{ code: "42P01", status: 404 },
|
|
209
268
|
{ code: "42P17", status: 500 },
|
|
210
|
-
{ code: "42501", status: 403 }
|
|
269
|
+
{ code: "42501", status: 403 }
|
|
211
270
|
];
|
|
212
271
|
|
|
213
272
|
// Return custom error if Postgrest error code is found
|
|
@@ -241,7 +300,8 @@ export const Utility = {
|
|
|
241
300
|
* @param {string} input - The input to be sanitised
|
|
242
301
|
* @returns A sanitised string, or an empty string if the input is invalid
|
|
243
302
|
*/
|
|
244
|
-
sanitiseHtml: (input: string) =>
|
|
303
|
+
sanitiseHtml: (input: string) =>
|
|
304
|
+
typeof input !== "string" ? "" : sanitize(input, { allowedTags: [], allowedAttributes: {} }),
|
|
245
305
|
/**
|
|
246
306
|
* A regular expression for social media handles, without a leading slash or @ character.
|
|
247
307
|
*
|
|
@@ -254,11 +314,15 @@ export const Utility = {
|
|
|
254
314
|
* @param {"any" | MobilePhoneLocale | MobilePhoneLocale[]} [locale="any"] - The locale(s) to validate the phone number against
|
|
255
315
|
* @returns `true` if the phone number is valid; `false` otherwise
|
|
256
316
|
*/
|
|
257
|
-
validateE164: (phoneNumber: string, locale: "any" | MobilePhoneLocale | MobilePhoneLocale[] = "en-KE") =>
|
|
317
|
+
validateE164: (phoneNumber: string, locale: "any" | MobilePhoneLocale | MobilePhoneLocale[] = "en-KE") =>
|
|
318
|
+
isMobilePhone(phoneNumber, locale) && Boolean(phoneNumber.match(Utility.e164Regex)),
|
|
258
319
|
/**
|
|
259
320
|
* Validates a social media handle.
|
|
260
321
|
* @param {string} handle - The social media handle to be validated
|
|
261
322
|
* @returns `true` if the handle is valid; `false` otherwise
|
|
262
323
|
*/
|
|
263
|
-
validateSocialMediaHandle: (handle: string) =>
|
|
324
|
+
validateSocialMediaHandle: (handle: string) =>
|
|
325
|
+
!isURL(handle, { require_protocol: false }) &&
|
|
326
|
+
!handle.startsWith("@") &&
|
|
327
|
+
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
264
328
|
};
|