feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,95 @@
import { VALID_DIGITS } from '../../constants.js';
// The RFC 3966 format for extensions.
var RFC3966_EXTN_PREFIX = ';ext=';
/**
* Helper method for constructing regular expressions for parsing. Creates
* an expression that captures up to max_length digits.
* @return {string} RegEx pattern to capture extension digits.
*/
var getExtensionDigitsPattern = function getExtensionDigitsPattern(maxLength) {
return "([".concat(VALID_DIGITS, "]{1,").concat(maxLength, "})");
};
/**
* Helper initialiser method to create the regular-expression pattern to match
* extensions.
* Copy-pasted from Google's `libphonenumber`:
* https://github.com/google/libphonenumber/blob/55b2646ec9393f4d3d6661b9c82ef9e258e8b829/javascript/i18n/phonenumbers/phonenumberutil.js#L759-L766
* @return {string} RegEx pattern to capture extensions.
*/
export default function createExtensionPattern(purpose) {
// We cap the maximum length of an extension based on the ambiguity of the way
// the extension is prefixed. As per ITU, the officially allowed length for
// extensions is actually 40, but we don't support this since we haven't seen real
// examples and this introduces many false interpretations as the extension labels
// are not standardized.
/** @type {string} */
var extLimitAfterExplicitLabel = '20';
/** @type {string} */
var extLimitAfterLikelyLabel = '15';
/** @type {string} */
var extLimitAfterAmbiguousChar = '9';
/** @type {string} */
var extLimitWhenNotSure = '6';
/** @type {string} */
var possibleSeparatorsBetweenNumberAndExtLabel = "[ \xA0\\t,]*";
// Optional full stop (.) or colon, followed by zero or more spaces/tabs/commas.
/** @type {string} */
var possibleCharsAfterExtLabel = "[:\\.\uFF0E]?[ \xA0\\t,-]*";
/** @type {string} */
var optionalExtnSuffix = "#?";
// Here the extension is called out in more explicit way, i.e mentioning it obvious
// patterns like "ext.".
/** @type {string} */
var explicitExtLabels = "(?:e?xt(?:ensi(?:o\u0301?|\xF3))?n?|\uFF45?\uFF58\uFF54\uFF4E?|\u0434\u043E\u0431|anexo)";
// One-character symbols that can be used to indicate an extension, and less
// commonly used or more ambiguous extension labels.
/** @type {string} */
var ambiguousExtLabels = "(?:[x\uFF58#\uFF03~\uFF5E]|int|\uFF49\uFF4E\uFF54)";
// When extension is not separated clearly.
/** @type {string} */
var ambiguousSeparator = "[- ]+";
// This is the same as possibleSeparatorsBetweenNumberAndExtLabel, but not matching
// comma as extension label may have it.
/** @type {string} */
var possibleSeparatorsNumberExtLabelNoComma = "[ \xA0\\t]*";
// ",," is commonly used for auto dialling the extension when connected. First
// comma is matched through possibleSeparatorsBetweenNumberAndExtLabel, so we do
// not repeat it here. Semi-colon works in Iphone and Android also to pop up a
// button with the extension number following.
/** @type {string} */
var autoDiallingAndExtLabelsFound = "(?:,{2}|;)";
/** @type {string} */
var rfcExtn = RFC3966_EXTN_PREFIX + getExtensionDigitsPattern(extLimitAfterExplicitLabel);
/** @type {string} */
var explicitExtn = possibleSeparatorsBetweenNumberAndExtLabel + explicitExtLabels + possibleCharsAfterExtLabel + getExtensionDigitsPattern(extLimitAfterExplicitLabel) + optionalExtnSuffix;
/** @type {string} */
var ambiguousExtn = possibleSeparatorsBetweenNumberAndExtLabel + ambiguousExtLabels + possibleCharsAfterExtLabel + getExtensionDigitsPattern(extLimitAfterAmbiguousChar) + optionalExtnSuffix;
/** @type {string} */
var americanStyleExtnWithSuffix = ambiguousSeparator + getExtensionDigitsPattern(extLimitWhenNotSure) + "#";
/** @type {string} */
var autoDiallingExtn = possibleSeparatorsNumberExtLabelNoComma + autoDiallingAndExtLabelsFound + possibleCharsAfterExtLabel + getExtensionDigitsPattern(extLimitAfterLikelyLabel) + optionalExtnSuffix;
/** @type {string} */
var onlyCommasExtn = possibleSeparatorsNumberExtLabelNoComma + "(?:,)+" + possibleCharsAfterExtLabel + getExtensionDigitsPattern(extLimitAfterAmbiguousChar) + optionalExtnSuffix;
// The first regular expression covers RFC 3966 format, where the extension is added
// using ";ext=". The second more generic where extension is mentioned with explicit
// labels like "ext:". In both the above cases we allow more numbers in extension than
// any other extension labels. The third one captures when single character extension
// labels or less commonly used labels are used. In such cases we capture fewer
// extension digits in order to reduce the chance of falsely interpreting two
// numbers beside each other as a number + extension. The fourth one covers the
// special case of American numbers where the extension is written with a hash
// at the end, such as "- 503#". The fifth one is exclusively for extension
// autodialling formats which are used when dialling and in this case we accept longer
// extensions. The last one is more liberal on the number of commas that acts as
// extension labels, so we have a strict cap on the number of digits in such extensions.
return rfcExtn + "|" + explicitExtn + "|" + ambiguousExtn + "|" + americanStyleExtnWithSuffix + "|" + autoDiallingExtn + "|" + onlyCommasExtn;
}
//# sourceMappingURL=createExtensionPattern.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,30 @@
import createExtensionPattern from './createExtensionPattern.js';
// Regexp of all known extension prefixes used by different regions followed by
// 1 or more valid digits, for use when parsing.
var EXTN_PATTERN = new RegExp('(?:' + createExtensionPattern() + ')$', 'i');
// Strips any extension (as in, the part of the number dialled after the call is
// connected, usually indicated with extn, ext, x or similar) from the end of
// the number, and returns it.
export default function extractExtension(number) {
var start = number.search(EXTN_PATTERN);
if (start < 0) {
return {};
}
// If we find a potential extension, and the number preceding this is a viable
// number, we assume it is an extension.
var numberWithoutExtension = number.slice(0, start);
var matches = number.match(EXTN_PATTERN);
var i = 1;
while (i < matches.length) {
if (matches[i]) {
return {
number: numberWithoutExtension,
ext: matches[i]
};
}
i++;
}
}
//# sourceMappingURL=extractExtension.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extractExtension.js","names":["createExtensionPattern","EXTN_PATTERN","RegExp","extractExtension","number","start","search","numberWithoutExtension","slice","matches","match","i","length","ext"],"sources":["../../../source/helpers/extension/extractExtension.js"],"sourcesContent":["import createExtensionPattern from './createExtensionPattern.js'\r\n\r\n// Regexp of all known extension prefixes used by different regions followed by\r\n// 1 or more valid digits, for use when parsing.\r\nconst EXTN_PATTERN = new RegExp('(?:' + createExtensionPattern() + ')$', 'i')\r\n\r\n// Strips any extension (as in, the part of the number dialled after the call is\r\n// connected, usually indicated with extn, ext, x or similar) from the end of\r\n// the number, and returns it.\r\nexport default function extractExtension(number) {\r\n\tconst start = number.search(EXTN_PATTERN)\r\n\tif (start < 0) {\r\n\t\treturn {}\r\n\t}\r\n\t// If we find a potential extension, and the number preceding this is a viable\r\n\t// number, we assume it is an extension.\r\n\tconst numberWithoutExtension = number.slice(0, start)\r\n\tconst matches = number.match(EXTN_PATTERN)\r\n\tlet i = 1\r\n\twhile (i < matches.length) {\r\n\t\tif (matches[i]) {\r\n\t\t\treturn {\r\n\t\t\t\tnumber: numberWithoutExtension,\r\n\t\t\t\text: matches[i]\r\n\t\t\t}\r\n\t\t}\r\n\t\ti++\r\n\t}\r\n}"],"mappings":"AAAA,OAAOA,sBAAsB,MAAM,6BAA6B;;AAEhE;AACA;AACA,IAAMC,YAAY,GAAG,IAAIC,MAAM,CAAC,KAAK,GAAGF,sBAAsB,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC;;AAE7E;AACA;AACA;AACA,eAAe,SAASG,gBAAgBA,CAACC,MAAM,EAAE;EAChD,IAAMC,KAAK,GAAGD,MAAM,CAACE,MAAM,CAACL,YAAY,CAAC;EACzC,IAAII,KAAK,GAAG,CAAC,EAAE;IACd,OAAO,CAAC,CAAC;EACV;EACA;EACA;EACA,IAAME,sBAAsB,GAAGH,MAAM,CAACI,KAAK,CAAC,CAAC,EAAEH,KAAK,CAAC;EACrD,IAAMI,OAAO,GAAGL,MAAM,CAACM,KAAK,CAACT,YAAY,CAAC;EAC1C,IAAIU,CAAC,GAAG,CAAC;EACT,OAAOA,CAAC,GAAGF,OAAO,CAACG,MAAM,EAAE;IAC1B,IAAIH,OAAO,CAACE,CAAC,CAAC,EAAE;MACf,OAAO;QACNP,MAAM,EAAEG,sBAAsB;QAC9BM,GAAG,EAAEJ,OAAO,CAACE,CAAC;MACf,CAAC;IACF;IACAA,CAAC,EAAE;EACJ;AACD","ignoreList":[]}