@koloseum/utils 0.2.7 → 0.2.9
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 +28 -1
- package/dist/utils.js +196 -8
- package/package.json +4 -2
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "@koloseum/types/database";
|
|
2
2
|
import type { CustomError, Microservice, MicroserviceGroup, MicroserviceObject, UserWithCustomMetadata } from "@koloseum/types/general";
|
|
3
|
-
import type { IdentityType, PronounsCheckboxes, PronounsItem, Sex, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
3
|
+
import type { BranchAddressObject, County, IdentityType, PronounsCheckboxes, PronounsItem, Sex, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
4
4
|
import type { Page } from "@sveltejs/kit";
|
|
5
5
|
import type { SupabaseClient, FunctionInvokeOptions, PostgrestError } from "@supabase/supabase-js";
|
|
6
6
|
import type { IOptions } from "@suprsend/web-components/dist/types/interface.d.ts";
|
|
@@ -103,6 +103,12 @@ export declare const Utility: {
|
|
|
103
103
|
* @returns The formatted identity type string
|
|
104
104
|
*/
|
|
105
105
|
getIdentityTypeString: (identityType: IdentityType) => string;
|
|
106
|
+
/**
|
|
107
|
+
* Returns a list of all counties in Kenya.
|
|
108
|
+
* @param {"name" | "code"} sortBy - The field to sort the counties by, i.e. `name` or `code`; defaults to `name`
|
|
109
|
+
* @returns {Promise<County[]>} A list of objects with the county `name`, `code`, and a list of `subCounties`
|
|
110
|
+
*/
|
|
111
|
+
getKenyaCounties: (sortBy?: "name" | "code") => Promise<County[]>;
|
|
106
112
|
/**
|
|
107
113
|
* Returns the URL for a menu item based on the slug.
|
|
108
114
|
* @param {string} base - The base URL
|
|
@@ -183,6 +189,18 @@ export declare const Utility: {
|
|
|
183
189
|
* @returns `true` if the page is active, `false` otherwise.
|
|
184
190
|
*/
|
|
185
191
|
pageIsActive: (page: Page, slug: string | undefined, microservice?: Microservice<MicroserviceGroup>, base?: string) => boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Paginates an array.
|
|
194
|
+
* @template T - The type of array items; defaults to `any`
|
|
195
|
+
* @param {T[]} array - The array to paginate
|
|
196
|
+
* @param {number} page - The page number
|
|
197
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
198
|
+
* @returns {Object} The paginated array and total number of pages
|
|
199
|
+
*/
|
|
200
|
+
paginateArray: <T = any>(array: T[], page: number, pageSize?: number) => {
|
|
201
|
+
paginatedItems: T[];
|
|
202
|
+
totalPages: number;
|
|
203
|
+
};
|
|
186
204
|
/**
|
|
187
205
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
188
206
|
* @param {CustomError} error - The error object
|
|
@@ -224,6 +242,15 @@ export declare const Utility: {
|
|
|
224
242
|
* - Source: https://stackoverflow.com/a/74579651
|
|
225
243
|
*/
|
|
226
244
|
socialMediaHandleRegex: RegExp;
|
|
245
|
+
/**
|
|
246
|
+
* Validates address data submitted in a form and returns the validated data.
|
|
247
|
+
* @param {FormData} formData - The submitted form data
|
|
248
|
+
* @returns An object with the validated `address`, or an `error` if any has occurred
|
|
249
|
+
*/
|
|
250
|
+
validateAddress: (formData: FormData) => Promise<{
|
|
251
|
+
address?: BranchAddressObject;
|
|
252
|
+
error?: CustomError;
|
|
253
|
+
}>;
|
|
227
254
|
/**
|
|
228
255
|
* Validates an E.164 phone number.
|
|
229
256
|
* @param {string} phoneNumber - The phone number to be validated
|
package/dist/utils.js
CHANGED
|
@@ -2,10 +2,12 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
2
2
|
import { error as svelteError } from "@sveltejs/kit";
|
|
3
3
|
import { FunctionsFetchError, FunctionsHttpError, FunctionsRelayError } from "@supabase/supabase-js";
|
|
4
4
|
import validator from "validator";
|
|
5
|
-
/*
|
|
5
|
+
/* HELPERS */
|
|
6
6
|
const sanitize = (await import("sanitize-html")).default;
|
|
7
|
-
const { isMobilePhone, isURL } = validator;
|
|
8
|
-
|
|
7
|
+
const { trim, escape, isMobilePhone, isURL } = validator;
|
|
8
|
+
const { KenyaAdministrativeDivisions } = (await import("kenya-administrative-divisions"));
|
|
9
|
+
const kenyaAdmin = new KenyaAdministrativeDivisions();
|
|
10
|
+
/* DUMMY DATA */
|
|
9
11
|
export const Data = {
|
|
10
12
|
/**
|
|
11
13
|
* A generic authenticated user.
|
|
@@ -68,7 +70,7 @@ export const Data = {
|
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
};
|
|
71
|
-
/*
|
|
73
|
+
/* STATUS MESSAGES */
|
|
72
74
|
export const Status = {
|
|
73
75
|
/**
|
|
74
76
|
* A generic error message.
|
|
@@ -83,7 +85,7 @@ export const Status = {
|
|
|
83
85
|
*/
|
|
84
86
|
PASSWORD_RESET_REQUESTED: "If the provided email address is registered, you will receive a password reset link shortly."
|
|
85
87
|
};
|
|
86
|
-
/*
|
|
88
|
+
/* UTILITY FUNCTIONS */
|
|
87
89
|
export const Utility = {
|
|
88
90
|
/**
|
|
89
91
|
* Calls a Supabase Edge function.
|
|
@@ -328,6 +330,32 @@ export const Utility = {
|
|
|
328
330
|
return "Driver's licence";
|
|
329
331
|
return "";
|
|
330
332
|
},
|
|
333
|
+
/**
|
|
334
|
+
* Returns a list of all counties in Kenya.
|
|
335
|
+
* @param {"name" | "code"} sortBy - The field to sort the counties by, i.e. `name` or `code`; defaults to `name`
|
|
336
|
+
* @returns {Promise<County[]>} A list of objects with the county `name`, `code`, and a list of `subCounties`
|
|
337
|
+
*/
|
|
338
|
+
getKenyaCounties: async (sortBy = "name") => {
|
|
339
|
+
// Get all counties
|
|
340
|
+
const countiesData = await kenyaAdmin.getAll();
|
|
341
|
+
// Format counties
|
|
342
|
+
const counties = countiesData.map((county) => {
|
|
343
|
+
// Get list of sub-counties
|
|
344
|
+
const subCounties = [];
|
|
345
|
+
for (const subCounty of county.constituencies)
|
|
346
|
+
subCounties.push(subCounty.constituency_name);
|
|
347
|
+
subCounties.sort();
|
|
348
|
+
// Format county name and add to list
|
|
349
|
+
const countyName = county.county_name.split(" ");
|
|
350
|
+
let name = county.county_name;
|
|
351
|
+
if (countyName.length > 1)
|
|
352
|
+
name = countyName.map((word) => Utility.capitalise(word)).join(" ");
|
|
353
|
+
// Return county
|
|
354
|
+
return { name, code: county.county_code, subCounties };
|
|
355
|
+
});
|
|
356
|
+
// Return sorted counties
|
|
357
|
+
return counties.sort((a, b) => (sortBy === "name" ? a.name.localeCompare(b.name) : a.code - b.code));
|
|
358
|
+
},
|
|
331
359
|
/**
|
|
332
360
|
* Returns the URL for a menu item based on the slug.
|
|
333
361
|
* @param {string} base - The base URL
|
|
@@ -336,7 +364,7 @@ export const Utility = {
|
|
|
336
364
|
*/
|
|
337
365
|
getMenuItemUrl: (base, slug) => {
|
|
338
366
|
// Validate base URL
|
|
339
|
-
if (
|
|
367
|
+
if (typeof base !== "string")
|
|
340
368
|
return "";
|
|
341
369
|
// Return URL
|
|
342
370
|
return `${base}${slug && typeof slug === "string" ? `/${slug}` : ""}`;
|
|
@@ -348,7 +376,7 @@ export const Utility = {
|
|
|
348
376
|
*/
|
|
349
377
|
getParentUrl: (base) => {
|
|
350
378
|
// Validate input
|
|
351
|
-
if (
|
|
379
|
+
if (typeof base !== "string")
|
|
352
380
|
return "";
|
|
353
381
|
// Return parent URL
|
|
354
382
|
return base.replace(/\/$/, "").split("/").slice(0, -1).join("/") || "/";
|
|
@@ -971,7 +999,53 @@ export const Utility = {
|
|
|
971
999
|
{
|
|
972
1000
|
name: "Savanna FGC",
|
|
973
1001
|
description: "Manage fighting games esports data for Savanna FGC.",
|
|
974
|
-
slug: "fgc"
|
|
1002
|
+
slug: "fgc",
|
|
1003
|
+
tabs: [
|
|
1004
|
+
{
|
|
1005
|
+
name: "Savanna Circuit",
|
|
1006
|
+
description: "Manage competition data for the Savanna Circuit.",
|
|
1007
|
+
slug: "league",
|
|
1008
|
+
tabs: [
|
|
1009
|
+
{
|
|
1010
|
+
name: "Rankings",
|
|
1011
|
+
description: "Manage rankings for any Savanna Circuit season.",
|
|
1012
|
+
root: true
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
name: "Tournaments",
|
|
1016
|
+
description: "Manage tournaments for any Savanna Circuit season.",
|
|
1017
|
+
slug: "tournaments",
|
|
1018
|
+
tabs: [
|
|
1019
|
+
{
|
|
1020
|
+
name: "Events",
|
|
1021
|
+
description: "Manage events for any Savanna Circuit tournament.",
|
|
1022
|
+
root: true
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "Tickets",
|
|
1026
|
+
description: "Manage tickets for any Savanna Circuit tournament.",
|
|
1027
|
+
slug: "tickets"
|
|
1028
|
+
},
|
|
1029
|
+
{
|
|
1030
|
+
name: "Settings",
|
|
1031
|
+
description: "Manage settings for any Savanna Circuit tournament.",
|
|
1032
|
+
slug: "settings"
|
|
1033
|
+
}
|
|
1034
|
+
]
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
name: "Settings",
|
|
1038
|
+
description: "Manage settings for any Savanna Circuit season.",
|
|
1039
|
+
slug: "settings"
|
|
1040
|
+
}
|
|
1041
|
+
]
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
name: "Savanna Fight Night",
|
|
1045
|
+
description: "Manage competition data for Savanna Fight Night.",
|
|
1046
|
+
slug: "challenges"
|
|
1047
|
+
}
|
|
1048
|
+
]
|
|
975
1049
|
},
|
|
976
1050
|
{
|
|
977
1051
|
name: "Hit List",
|
|
@@ -1127,6 +1201,24 @@ export const Utility = {
|
|
|
1127
1201
|
const target = `${cleanBase}/${slug}`;
|
|
1128
1202
|
return page.url.pathname === target || page.url.pathname.startsWith(`${target}/`);
|
|
1129
1203
|
},
|
|
1204
|
+
/**
|
|
1205
|
+
* Paginates an array.
|
|
1206
|
+
* @template T - The type of array items; defaults to `any`
|
|
1207
|
+
* @param {T[]} array - The array to paginate
|
|
1208
|
+
* @param {number} page - The page number
|
|
1209
|
+
* @param {number} pageSize - The number of items per page; defaults to 10
|
|
1210
|
+
* @returns {Object} The paginated array and total number of pages
|
|
1211
|
+
*/
|
|
1212
|
+
paginateArray: (array, page, pageSize = 10) => {
|
|
1213
|
+
// Define variables
|
|
1214
|
+
const startIndex = (page - 1) * pageSize;
|
|
1215
|
+
const endIndex = startIndex + pageSize;
|
|
1216
|
+
const totalPages = Math.ceil(array.length / pageSize);
|
|
1217
|
+
// Paginate items
|
|
1218
|
+
const paginatedItems = array.slice(startIndex, endIndex);
|
|
1219
|
+
// Return data
|
|
1220
|
+
return { paginatedItems, totalPages };
|
|
1221
|
+
},
|
|
1130
1222
|
/**
|
|
1131
1223
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
1132
1224
|
* @param {CustomError} error - The error object
|
|
@@ -1226,6 +1318,102 @@ export const Utility = {
|
|
|
1226
1318
|
* - Source: https://stackoverflow.com/a/74579651
|
|
1227
1319
|
*/
|
|
1228
1320
|
socialMediaHandleRegex: /^([A-Za-z0-9_.]{3,25})$/gm,
|
|
1321
|
+
/**
|
|
1322
|
+
* Validates address data submitted in a form and returns the validated data.
|
|
1323
|
+
* @param {FormData} formData - The submitted form data
|
|
1324
|
+
* @returns An object with the validated `address`, or an `error` if any has occurred
|
|
1325
|
+
*/
|
|
1326
|
+
validateAddress: async (formData) => {
|
|
1327
|
+
// Building name
|
|
1328
|
+
let buildingName = formData.get("building-name");
|
|
1329
|
+
if (!buildingName)
|
|
1330
|
+
return { error: Utility.customError(400, "Building name is required.") };
|
|
1331
|
+
buildingName = Utility.sanitiseHtml(trim(escape(buildingName)));
|
|
1332
|
+
// Unit number
|
|
1333
|
+
let unitNumber = formData.get("unit-number") || undefined;
|
|
1334
|
+
if (unitNumber) {
|
|
1335
|
+
unitNumber = Utility.sanitiseHtml(trim(escape(unitNumber)));
|
|
1336
|
+
if (unitNumber === "")
|
|
1337
|
+
unitNumber = undefined;
|
|
1338
|
+
}
|
|
1339
|
+
// Street name
|
|
1340
|
+
let streetName = formData.get("street-name");
|
|
1341
|
+
if (!streetName)
|
|
1342
|
+
return { error: Utility.customError(400, "Street name is required.") };
|
|
1343
|
+
streetName = Utility.sanitiseHtml(trim(escape(streetName)));
|
|
1344
|
+
// P.O. box and postal code
|
|
1345
|
+
let boxNumber = formData.get("box-number") || undefined;
|
|
1346
|
+
let postalCode = formData.get("postal-code") || undefined;
|
|
1347
|
+
if (boxNumber) {
|
|
1348
|
+
boxNumber = Utility.sanitiseHtml(trim(escape(boxNumber)));
|
|
1349
|
+
if (boxNumber === "")
|
|
1350
|
+
boxNumber = undefined;
|
|
1351
|
+
if (boxNumber && !postalCode)
|
|
1352
|
+
return { error: Utility.customError(400, "Postal code is required.") };
|
|
1353
|
+
}
|
|
1354
|
+
if (postalCode) {
|
|
1355
|
+
postalCode = Utility.sanitiseHtml(trim(escape(postalCode)));
|
|
1356
|
+
if (postalCode === "")
|
|
1357
|
+
postalCode = undefined;
|
|
1358
|
+
if (postalCode && !boxNumber)
|
|
1359
|
+
return { error: Utility.customError(400, "P.O. box is required.") };
|
|
1360
|
+
}
|
|
1361
|
+
// Town
|
|
1362
|
+
let town = formData.get("town");
|
|
1363
|
+
if (!town)
|
|
1364
|
+
return { error: Utility.customError(400, "City/town is required.") };
|
|
1365
|
+
town = Utility.sanitiseHtml(trim(escape(town)));
|
|
1366
|
+
// County
|
|
1367
|
+
let county = undefined;
|
|
1368
|
+
const countyCode = Number(formData.get("county-code"));
|
|
1369
|
+
if (isNaN(countyCode))
|
|
1370
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1371
|
+
if (!countyCode)
|
|
1372
|
+
return { error: Utility.customError(400, "County is required.") };
|
|
1373
|
+
const counties = await kenyaAdmin.getAll();
|
|
1374
|
+
if (!counties || counties.length === 0)
|
|
1375
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1376
|
+
const countyData = counties.find(({ county_code }) => county_code === countyCode);
|
|
1377
|
+
if (!countyData)
|
|
1378
|
+
return { error: Utility.customError(400, "County is invalid.") };
|
|
1379
|
+
const { county_name, constituencies } = countyData;
|
|
1380
|
+
county = county_name;
|
|
1381
|
+
// Sub-county
|
|
1382
|
+
let subCounty = formData.get("sub-county") || undefined;
|
|
1383
|
+
if (subCounty) {
|
|
1384
|
+
subCounty = Utility.sanitiseHtml(trim(escape(subCounty)));
|
|
1385
|
+
if (subCounty === "")
|
|
1386
|
+
subCounty = undefined;
|
|
1387
|
+
else {
|
|
1388
|
+
// Check if constituencies exists and is an array before mapping
|
|
1389
|
+
if (!constituencies || !Array.isArray(constituencies))
|
|
1390
|
+
return { error: Utility.customError(400, "Sub-county data is unavailable.") };
|
|
1391
|
+
// Get list of sub-counties
|
|
1392
|
+
const subCounties = constituencies.map(({ constituency_name: name }) => name);
|
|
1393
|
+
if (!subCounties.includes(subCounty))
|
|
1394
|
+
return { error: Utility.customError(400, "Sub-county is invalid.") };
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// Additional information
|
|
1398
|
+
let additionalInfo = formData.get("address-additional-info") || undefined;
|
|
1399
|
+
if (additionalInfo) {
|
|
1400
|
+
additionalInfo = Utility.sanitiseHtml(trim(escape(additionalInfo)));
|
|
1401
|
+
if (additionalInfo === "")
|
|
1402
|
+
additionalInfo = undefined;
|
|
1403
|
+
}
|
|
1404
|
+
// Create address object
|
|
1405
|
+
const address = {
|
|
1406
|
+
buildingName,
|
|
1407
|
+
unitNumber,
|
|
1408
|
+
streetName,
|
|
1409
|
+
town,
|
|
1410
|
+
subCounty,
|
|
1411
|
+
county,
|
|
1412
|
+
additionalInfo
|
|
1413
|
+
};
|
|
1414
|
+
// Return data
|
|
1415
|
+
return { address };
|
|
1416
|
+
},
|
|
1229
1417
|
/**
|
|
1230
1418
|
* Validates an E.164 phone number.
|
|
1231
1419
|
* @param {string} phoneNumber - The phone number to be validated
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -31,17 +31,19 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@supabase/supabase-js": "^2.50.1",
|
|
33
33
|
"@sveltejs/kit": "^2.22.0",
|
|
34
|
+
"kenya-administrative-divisions": "^0.0.18",
|
|
34
35
|
"sanitize-html": "^2.17.0",
|
|
35
36
|
"uuid": "^11.1.0",
|
|
36
37
|
"validator": "^13.15.15"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@koloseum/types": "^0.2.
|
|
40
|
+
"@koloseum/types": "^0.2.4",
|
|
40
41
|
"@playwright/test": "^1.53.1",
|
|
41
42
|
"@suprsend/web-components": "^0.2.1",
|
|
42
43
|
"@types/sanitize-html": "^2.16.0",
|
|
43
44
|
"@types/uuid": "^10.0.0",
|
|
44
45
|
"@types/validator": "^13.15.2",
|
|
46
|
+
"postgres-interval": "^4.0.2",
|
|
45
47
|
"prettier": "^3.6.0",
|
|
46
48
|
"typescript": "^5.8.3",
|
|
47
49
|
"vitest": "^3.2.4"
|