mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-07 23:13:57 -05:00
Update JSDoc and documentation standards for JS/TS
Expanded and clarified documentation standards in javatsscript.instructions.md, including detailed JSDoc rules and examples for functions, classes, interfaces, and constants. This update aims to improve code clarity and maintainability by enforcing consistent and concise documentation practices.
This commit is contained in:
113
.github/instructions/javatsscript.instructions.md
vendored
113
.github/instructions/javatsscript.instructions.md
vendored
@@ -35,27 +35,100 @@ applyTo: '**/*.ts,**/*.js,**/*.vue'
|
||||
- No implicit `any` types
|
||||
- Centralized imports from `/types/index.ts`
|
||||
|
||||
# Component Structure (10 Sections)
|
||||
|
||||
All Vue components must follow this exact structure:
|
||||
|
||||
1. **Fileoverview JSDoc** - Clear, purposeful component
|
||||
description
|
||||
2. **Imports** - All dependencies
|
||||
3. **Composables & Stores** - Router, stores, composables
|
||||
4. **Reactive State** - ref() declarations
|
||||
5. **Computed Properties** - computed() declarations
|
||||
6. **UI Interaction Handlers** - Button clicks, form events
|
||||
7. **Validation Logic** - Form validation functions
|
||||
8. **Main Logic** - Core business logic
|
||||
9. **Lifecycle Hooks** - onMounted, onBeforeUnmount, etc.
|
||||
10. **Component Definition** - defineExpose if needed
|
||||
|
||||
# Documentation Standards
|
||||
- Each component must include clear, purposeful JSDoc overview
|
||||
- Document complex logic
|
||||
- Skip redundant auto-generated comments
|
||||
- Focus on "why" not "what" for non-obvious code
|
||||
|
||||
## General Rules
|
||||
|
||||
- **Follow JSDoc format** — use standard `/** ... */` block comments.
|
||||
- **Always include `@param`, `@returns`, and `@throws`** sections — even when parameters or behavior seem obvious.
|
||||
- **Do not include examples** inside docstrings — keep them in external documentation or test cases.
|
||||
- **Avoid extended explanations** — use a single summary line followed by concise sections.
|
||||
- **Keep comments concise and descriptive** — explain *what* the code does, not *how* it does it.
|
||||
- **Use Markdown formatting** (`code`, *italic*, **bold**) inside comments for clarity.
|
||||
|
||||
## Function Documentation
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Adds two numbers together.
|
||||
*
|
||||
* @param a - The first number.
|
||||
* @param b - The second number.
|
||||
* @returns The sum of `a` and `b`.
|
||||
*/
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
## Class Documentation
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Document the class itself with a one-line summary.
|
||||
- Each public method and property should have its own concise JSDoc block.
|
||||
- Avoid documenting private members unless needed for internal tooling or generated docs.
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Represents a user in the system.
|
||||
*
|
||||
* @remarks
|
||||
* Keep descriptions brief; focus on what the class represents or provides.
|
||||
*/
|
||||
class User {
|
||||
/**
|
||||
* Creates a new user instance.
|
||||
*
|
||||
* @param name - The user’s display name.
|
||||
*/
|
||||
constructor(public name: string) {}
|
||||
|
||||
/**
|
||||
* Returns a greeting for the user.
|
||||
*
|
||||
* @returns A personalized greeting message.
|
||||
*/
|
||||
greet(): string {
|
||||
return `Hello, ${this.name}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Interface / Type Documentation
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Document each property using @property.
|
||||
- Avoid repeating type information already in code.
|
||||
- Keep to one concise sentence per property.
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Describes a basic user profile.
|
||||
*
|
||||
* @property id - Unique identifier for the user.
|
||||
* @property name - Full name of the user.
|
||||
* @property isAdmin - Whether the user has administrative privileges.
|
||||
*/
|
||||
interface UserProfile {
|
||||
id: string;
|
||||
name: string;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
## Variable / Constant Documentation
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Use single-line comments for constants or simple variables.
|
||||
- If a variable is complex or derived, use a full JSDoc block with a one-line summary and @type if helpful.
|
||||
|
||||
```ts
|
||||
/** The maximum number of users allowed in the system. */
|
||||
const MAX_USERS = 100;
|
||||
```
|
||||
|
||||
# Centralized Architecture
|
||||
|
||||
|
||||
248
backend/poetry.lock
generated
248
backend/poetry.lock
generated
@@ -79,14 +79,14 @@ windows = ["pywin32", "tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "apscheduler"
|
||||
version = "3.11.0"
|
||||
version = "3.11.1"
|
||||
description = "In-process task scheduler with Cron-like capabilities"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"},
|
||||
{file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"},
|
||||
{file = "apscheduler-3.11.1-py3-none-any.whl", hash = "sha256:6162cb5683cb09923654fa9bdd3130c4be4bfda6ad8990971c9597ecd52965d2"},
|
||||
{file = "apscheduler-3.11.1.tar.gz", hash = "sha256:0db77af6400c84d1747fe98a04b8b58f0080c77d11d338c4f507a9752880f221"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1166,14 +1166,14 @@ testing = ["coverage", "pytest", "pytest-vcr (>=1.0.2)", "vcrpy (>=7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "garth"
|
||||
version = "0.5.18"
|
||||
version = "0.5.19"
|
||||
description = "Garmin SSO auth + Connect client"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "garth-0.5.18-py3-none-any.whl", hash = "sha256:b3efbe4c7f813a69a4f161e2b1f8d23813fd5d026c198cd9cfff2d5fed8adbf6"},
|
||||
{file = "garth-0.5.18.tar.gz", hash = "sha256:f837f8d19623e7e568f4814517ef707c688abe10965b2ee1aa1a015592ac771d"},
|
||||
{file = "garth-0.5.19-py3-none-any.whl", hash = "sha256:43de822f8c546ac45ecef19baaf88fd2c5b7ef16ae06e8c4c654ef83582c0eb1"},
|
||||
{file = "garth-0.5.19.tar.gz", hash = "sha256:9e8f6ac43b64ea4e585ce13015fc0f25be04ca541128e1a8ad5220be1c1de1d6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2285,14 +2285,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pbs-installer"
|
||||
version = "2025.10.28"
|
||||
version = "2025.10.31"
|
||||
description = "Installer for Python Build Standalone"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pbs_installer-2025.10.28-py3-none-any.whl", hash = "sha256:329b0800df9ff8d50c79bfead69b0e05fa5c81d31e7e77377d1c422f94407eda"},
|
||||
{file = "pbs_installer-2025.10.28.tar.gz", hash = "sha256:399f1788b17c650e69c42729ba9e74d240909f36cfe187b5f9b60488314ba154"},
|
||||
{file = "pbs_installer-2025.10.31-py3-none-any.whl", hash = "sha256:f24e8f01f13fee9a203feb62e90c8d204d64d71ad0a3f3abfd69673b02bbc68b"},
|
||||
{file = "pbs_installer-2025.10.31.tar.gz", hash = "sha256:8529dbac1054408ccce5fb218a85a2d4a02f3657ddbde56eff79464105bba659"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2415,14 +2415,14 @@ xmp = ["defusedxml"]
|
||||
|
||||
[[package]]
|
||||
name = "pint"
|
||||
version = "0.25"
|
||||
version = "0.25.1"
|
||||
description = "Physical quantities module"
|
||||
optional = false
|
||||
python-versions = ">=3.11"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pint-0.25-py3-none-any.whl", hash = "sha256:cc20ae3dff010b9bbea41fb80c2de008f683cc83512cea73633d55aead80aa1e"},
|
||||
{file = "pint-0.25.tar.gz", hash = "sha256:22911a30d682ee0540d656571c19a7b1806ce00b2be88a16f67218108b7b8cc2"},
|
||||
{file = "pint-0.25.1-py3-none-any.whl", hash = "sha256:b13dc42d0effa2d98b621b06eb0f2990d262c655c8893f6d40a74c334f9aa6b4"},
|
||||
{file = "pint-0.25.1.tar.gz", hash = "sha256:34e6f89bfbfca94f29bde65c9ea42c1c56e05426692c655a3ee6f3ca2a92d252"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2436,7 +2436,7 @@ all = ["babel (<=2.8)", "dask (<2025.3.0)", "matplotlib", "mip (>=1.13) ; python
|
||||
babel = ["babel (<=2.8)"]
|
||||
codspeed = ["pytest", "pytest-benchmark", "pytest-codspeed", "pytest-cov", "pytest-mpl", "pytest-subtests"]
|
||||
dask = ["dask (<2025.3.0)"]
|
||||
docs = ["babel", "commonmark (==0.8.1)", "docutils", "graphviz", "ipykernel", "ipython (<=8.12)", "jupyter-client", "nbsphinx", "pooch", "pygments (>=2.4)", "recommonmark (==0.5.0)", "sciform", "scipy", "serialize", "sparse", "sphinx (>=6,<8.2)", "sphinx-book-theme (>=1.1.0)", "sphinx-copybutton", "sphinx-design"]
|
||||
docs = ["babel", "commonmark (==0.8.1)", "currencyconverter", "docutils", "graphviz", "ipykernel", "ipython (<=8.12)", "jupyter-client", "nbsphinx", "pooch", "pygments (>=2.4)", "recommonmark (==0.5.0)", "sciform", "scipy", "serialize", "sparse", "sphinx (>=6,<8.2)", "sphinx-book-theme (>=1.1.0)", "sphinx-copybutton", "sphinx-design"]
|
||||
matplotlib = ["matplotlib"]
|
||||
mip = ["mip (>=1.13) ; python_version < \"3.13\""]
|
||||
numpy = ["numpy (>=1.23)"]
|
||||
@@ -2565,35 +2565,35 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "psutil"
|
||||
version = "7.1.2"
|
||||
version = "7.1.3"
|
||||
description = "Cross-platform lib for process and system monitoring."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "psutil-7.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0cc5c6889b9871f231ed5455a9a02149e388fffcb30b607fb7a8896a6d95f22e"},
|
||||
{file = "psutil-7.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8e9e77a977208d84aa363a4a12e0f72189d58bbf4e46b49aae29a2c6e93ef206"},
|
||||
{file = "psutil-7.1.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d9623a5e4164d2220ecceb071f4b333b3c78866141e8887c072129185f41278"},
|
||||
{file = "psutil-7.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:364b1c10fe4ed59c89ec49e5f1a70da353b27986fa8233b4b999df4742a5ee2f"},
|
||||
{file = "psutil-7.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f101ef84de7e05d41310e3ccbdd65a6dd1d9eed85e8aaf0758405d022308e204"},
|
||||
{file = "psutil-7.1.2-cp313-cp313t-win_arm64.whl", hash = "sha256:20c00824048a95de67f00afedc7b08b282aa08638585b0206a9fb51f28f1a165"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:e09cfe92aa8e22b1ec5e2d394820cf86c5dff6367ac3242366485dfa874d43bc"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa6342cf859c48b19df3e4aa170e4cfb64aadc50b11e06bb569c6c777b089c9e"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:625977443498ee7d6c1e63e93bacca893fd759a66c5f635d05e05811d23fb5ee"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a24bcd7b7f2918d934af0fb91859f621b873d6aa81267575e3655cd387572a7"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:329f05610da6380982e6078b9d0881d9ab1e9a7eb7c02d833bfb7340aa634e31"},
|
||||
{file = "psutil-7.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7b04c29e3c0c888e83ed4762b70f31e65c42673ea956cefa8ced0e31e185f582"},
|
||||
{file = "psutil-7.1.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c9ba5c19f2d46203ee8c152c7b01df6eec87d883cfd8ee1af2ef2727f6b0f814"},
|
||||
{file = "psutil-7.1.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:2a486030d2fe81bec023f703d3d155f4823a10a47c36784c84f1cc7f8d39bedb"},
|
||||
{file = "psutil-7.1.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3efd8fc791492e7808a51cb2b94889db7578bfaea22df931424f874468e389e3"},
|
||||
{file = "psutil-7.1.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2aeb9b64f481b8eabfc633bd39e0016d4d8bbcd590d984af764d80bf0851b8a"},
|
||||
{file = "psutil-7.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:8e17852114c4e7996fe9da4745c2bdef001ebbf2f260dec406290e66628bdb91"},
|
||||
{file = "psutil-7.1.2-cp37-abi3-win_arm64.whl", hash = "sha256:3e988455e61c240cc879cb62a008c2699231bf3e3d061d7fce4234463fd2abb4"},
|
||||
{file = "psutil-7.1.2.tar.gz", hash = "sha256:aa225cdde1335ff9684708ee8c72650f6598d5ed2114b9a7c5802030b1785018"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa"},
|
||||
{file = "psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7"},
|
||||
{file = "psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264"},
|
||||
{file = "psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab"},
|
||||
{file = "psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880"},
|
||||
{file = "psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3"},
|
||||
{file = "psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b"},
|
||||
{file = "psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd"},
|
||||
{file = "psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1"},
|
||||
{file = "psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""]
|
||||
dev = ["abi3audit", "black", "check-manifest", "colorama ; os_name == \"nt\"", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pyreadline ; os_name == \"nt\"", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "validate-pyproject[all]", "virtualenv", "vulture", "wheel", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""]
|
||||
test = ["pytest", "pytest-instafail", "pytest-subtests", "pytest-xdist", "pywin32 ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "setuptools", "wheel ; os_name == \"nt\" and platform_python_implementation != \"PyPy\"", "wmi ; os_name == \"nt\" and platform_python_implementation != \"PyPy\""]
|
||||
|
||||
[[package]]
|
||||
@@ -3182,101 +3182,95 @@ png = ["pypng"]
|
||||
|
||||
[[package]]
|
||||
name = "rapidfuzz"
|
||||
version = "3.14.2"
|
||||
version = "3.14.3"
|
||||
description = "rapid fuzzy string matching"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:37ddc4cc3eafe29ec8ba451fcec5244af441eeb53b4e7b4d1d886cd3ff3624f4"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:654be63b17f3da8414968dfdf15c46c8205960ec8508cbb9d837347bf036dc0b"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75866e9fa474ccfe6b77367fb7c10e6f9754fb910d9b110490a6fad25501a039"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd915693a8d441e5f277bef23065275a2bb492724b5ccf64e38e60edd702b0fb"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e702e76a6166bff466a33888902404209fffd83740d24918ef74514542f66367"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78f84592f3a2f2773d6f411b755d683b1ce7f05adff4c12c0de923d5f2786e51"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:36d43c9f1b88322ad05b22fa80b6b4a95d2b193d392d3aa7bee652c144cfb1d9"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69d6f93916717314209f4e8701d203876baeadf8c9dcaee961b8afeba7435643"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e262958d3ca723c1ce32030384a1626e3d43ba7465e01a3e2b633f4300956150"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:26b5e6e0d39337431ab1b36faf604873cb1f0de9280e0703f61c6753c8fa1f7f"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2aad09712e1ffbc00ac25f12646c7065b84496af7cd0a70b1d5aff6318405732"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f10dbbafa3decee704b7a02ffe7914d7dfbbd3d1fce7f37ed2c3d6c3a7c9a8e6"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-win32.whl", hash = "sha256:6c3dab8f9d4271e32c8746461a58412871ebb07654f77aa6121961e796482d30"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-win_amd64.whl", hash = "sha256:5386ce287e5b71db4fd71747a23ae0ca5053012dc959049e160857c5fdadf6cd"},
|
||||
{file = "rapidfuzz-3.14.2-cp310-cp310-win_arm64.whl", hash = "sha256:c78d6f205b871f2d41173f82ded66bcef2f692e1b90c0f627cc8035b72898f35"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3969670d4b85e589564d6a75638ec2372a4375b7e68e747f3bd37b507cf843e4"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:061884b23a8c5eea9443e52acf02cbd533aff93a5439b0e90b5586a0638b8720"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6fc2bc48a219c171deb8529bfcc90ca6663fbcaa42b54ef202858976078f858a"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfa62729ac2d77a50a240b6331e9fffb5e070625e97e8f7e50fa882b3ea396ad"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2d001aaf47a500083b189140df16eaefd675bf06c818a71ae9f687b0d6f804f8"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c95eeaa7f2a990757826aa34e7375b50d49172da5ca7536dc461b1d197e0de9b"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:30af5e015462f89408d7b3bbdd614c739adc386e3d47bd565b53ffb670266021"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:35f12b07d58b932ef95b5f66b40c9efc60c5201bccd3c5ddde4a87df19d0aba8"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0aa67110e016d2cdce3e5a3330d09fb1dba3cf83350f6eb46a6b9276cbafd094"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b13dc4743a5d222600d98fb4a0345e910829ef4f286e81b34349627355884c87"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b16c40709f22c8fc16ca49a5484a468fe0a95f08f29c68043f46f8771e2c37e2"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac2bd7c74523f952a66536f72b3f68260427e2a6954f1f03d758f01bbbf60564"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-win32.whl", hash = "sha256:37d7045dc0ab4cab49d7cca66b651b44939e18e098a2f55466082e173b1aa452"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-win_amd64.whl", hash = "sha256:9a55ff35536662028563f22e0eadab47c7e94c8798239fe25d3ceca5ab156fd8"},
|
||||
{file = "rapidfuzz-3.14.2-cp311-cp311-win_arm64.whl", hash = "sha256:b2f0e1310f7cb1c0c0033987d0a0e85b4fd51a1c4882f556f082687d519f045d"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0418f6ac1da7adf7e6e469876508f63168e80d3265a9e7ab9a2e999020577bfa"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f6028090b49015fc9ff0df3c06751078fe300a291e933a378a7c37b78c4d6a3e"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21aa299985d1bbdb3ccf8a8214e7daee72bb7e8c8fb25a520f015dc200a57816"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e247612909876f36e6132265deef34efcaaf490e1857022204b206ff76578076"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9cf077475cd4118a5b846a72749d54b520243be6baddba1dd1446f3b1dbab29c"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5e7e02fb51f9a78e32f4fb8b5546d543e1fb637409cb682a6b8cb12e0c3015c"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:b1febabf4a4a664a2b6025830d93d7703f1cd9dcbe656ed7159053091b4d9389"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766d133f11888c48497f26a1722afc697a5fbad05bbfec3a41a4bc04fd21af9d"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:2a851a7c6660b6e47723378ca7692cd42700660a8783e4e7d07254a984d63ec8"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:686594bd7f7132cb85900a4cc910e9acb9d39466412b8a275f3d4bc37faba23c"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e1d412122de3c5c492acfcde020f543b9b529e2eb115f875e2fd7470e44ab441"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2611b1f6464dddf900bffeee2aa29a9aa1039317cbb226e18d3a5f029d4cf303"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-win32.whl", hash = "sha256:e6968b6db188fbb4c7a18aac25e075940a8204434a2a0d6bddb0a695d7f0c898"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-win_amd64.whl", hash = "sha256:1a6d43683c04ffb4270bb1498951a39e9c200eb326f933fd5d608c19485049b8"},
|
||||
{file = "rapidfuzz-3.14.2-cp312-cp312-win_arm64.whl", hash = "sha256:4ecd3ab9aebb17becb462eac19151bd143abc614e3d2a0351a72171371ac3f4b"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1f5a2566af7409d11f11b0b4e9f76a0ac64577737b821c64a2a6afc971c1c25"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:810863f3a98d09392e5fb481aef9d82597df6ee06f7f11ceafe6077585c4e018"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e8c0d16c0724dab7c7dc4099c1ec410679b2d11c1650b069d15d4ab4370f1cc"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:004f04356d84660feffbf8c26975cb0db0e010b2225d6e21b3d84dd8df764652"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3c2aea6b1db03a8abd62bb157161d7a65b896c9f85d5efc2f1bb444a107c47a"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bef63704b7851ad1adf5d7ceb7f1b3136b78ee0b34240c14ab85ea775f6caa7"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:52e8e37566313ac60bfa80754c4c0367eec65b3ef52bb8cc409b88e878b03182"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b3fad0fb5ac44944ad8f81e729ec45f65a85efb7d7ea4cf67343799c0ea9874b"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d027842a956b86aa9706b836c48186da405413d03957afaccda2fbe414bc3912"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:27dcb45427b1966fb43c904d19c841c3e6da147931959cf05388ecef9c5a1e8d"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1aab0676884e91282817b5710933efc4ea9466d2ba5703b5a7541468695d807a"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ef36c21ecb7f4bad7e4e119fe746a787ad684eaf1c383c17a2aff5d75b20fa58"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-win32.whl", hash = "sha256:ed3af4fa0dbd6d1964f171ac6fff82ed9e76c737eb34ae3daf926c4aefc2ce9b"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-win_amd64.whl", hash = "sha256:3fc2e7c3ab006299366b1c8256e452f00eb1659d0e4790b140633627c7d947b7"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313-win_arm64.whl", hash = "sha256:def48d5010ddcd2a80b44f14bf0172c29bfc27906d13c0ea69a6e3c00e6f225c"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a39952b8e033758ee15b2de48a5b0689c83ea6bd93c8df3635f2fbf21e52fd25"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f786811555869b5961b3718b007179e87d73c47414afee5fb882ae1b9b174c0c"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313t-win32.whl", hash = "sha256:6c0a25490a99c4b73f1deca3efae004df5f2b254760d98cac8d93becf41260d4"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e5af2dab8ec5a180d9ff24fbb5b25e589848b93cccb755eceb0bf0e3cfed7e5c"},
|
||||
{file = "rapidfuzz-3.14.2-cp313-cp313t-win_arm64.whl", hash = "sha256:8cf2aefb0d246d540ea83b4648db690bd7e25d34a7c23c5f250dcba2e4989192"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ace3a6b108679888833cdceea9a6231e406db202b8336eaf68279fe71a1d2ac4"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32c7cc978447202ba592e197228767b230d85e52e5ef229e2b22e51c8e3d06ad"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a479a824cbf6a646bcec1c34fbbfb85393d03eb2811657e3a6536298d435f76"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a3bc0c8b65dcd1e55a1cc42a7c7b34e93ad5d4bd1501dc998f4625042e1b110"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:217b46bf096818df16c0e2c43202aa8352e67c4379b1d5f25e98c5d1c7f5414d"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07d3e8afeeb81044873644e505e56ba06d8bdcc291ef7e26ac0f54c58309267d"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b7832c8707bfa4f9b081def64aa49954d4813cff7fc9ff4a0b184a4e8697147f"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:35581ba6981e016333063c52719c0b0b1bef0f944e641ad0f4ea34e0b39161f3"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:fbd5152169dc3f6c894c24fc04813f50bf9b929d137f2b965ac926e03329ceba"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98a119c3f9b152e9b62ec43520392669bd8deae9df269f30569f1c87bf6055a4"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9e84164e7a68f9c3523c5d104dda6601202b39bae0aac1b73a4f119d387275c4"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64c67402b86a073666f92c2807811e3817a17fedfe505fe89a9f93eea264481c"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-win32.whl", hash = "sha256:58d79f4df3e4332b31e671f9487f0c215856cf1f2d9ac3848ac10c27262fd723"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-win_amd64.whl", hash = "sha256:dc6fe7a27ad9e233c155e89b7e1d9b6d13963e3261ea5b30f3e79c3556c49bc9"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314-win_arm64.whl", hash = "sha256:bb4e96d80de7e6364850a2e168e899b8e85ab80ce19827cc4fbe0aa3c57f8124"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c7d4d0927a6b1ef2529a8cc57adf2ce965f7aaef324a4d1ae826d0de43ab4f82"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c0fae06e7fb4be18e86eb51e77f0d441975a3ba9ef963f957d750a2a41536ba1"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314t-win32.whl", hash = "sha256:d1d3ef72665d460b7b3e61d3dff4341a195dcb3250b4471eef71db23fca2d91a"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314t-win_amd64.whl", hash = "sha256:3a0960c5c11a34e8129a3062f1b1cbb371fad364e2195ebe46a88a9d5eeec0f1"},
|
||||
{file = "rapidfuzz-3.14.2-cp314-cp314t-win_arm64.whl", hash = "sha256:ed29600e55d7df104d5778d499678c305e32e3ccfa873489a7c8304489c5f8f3"},
|
||||
{file = "rapidfuzz-3.14.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:172630396d8bdbb5ea1a58e82afc489c8e18076e1f2b2edea20cb30f8926325a"},
|
||||
{file = "rapidfuzz-3.14.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6cff0d6749fac8dd7fdf26d0604d8a47c5ee786061972077d71ec7ac0fb7ced2"},
|
||||
{file = "rapidfuzz-3.14.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f558bc2ee3a0bb5d7238ed10a0b76455f2d28c97e93564a1f7855cea4096ef1c"},
|
||||
{file = "rapidfuzz-3.14.2.tar.gz", hash = "sha256:69bf91e66aeb84a104aea35e1b3f6b3aa606faaee6db1cfc76950f2a6a828a12"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b9fcd4d751a4fffa17aed1dde41647923c72c74af02459ad1222e3b0022da3a1"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4ad73afb688b36864a8d9b7344a9cf6da186c471e5790cbf541a635ee0f457f2"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5fb2d978a601820d2cfd111e2c221a9a7bfdf84b41a3ccbb96ceef29f2f1ac7"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d83b8b712fa37e06d59f29a4b49e2e9e8635e908fbc21552fe4d1163db9d2a1"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:dc8c07801df5206b81ed6bd6c35cb520cf9b6c64b9b0d19d699f8633dc942897"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c71ce6d4231e5ef2e33caa952bfe671cb9fd42e2afb11952df9fad41d5c821f9"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0e38828d1381a0cceb8a4831212b2f673d46f5129a1897b0451c883eaf4a1747"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da2a007434323904719158e50f3076a4dadb176ce43df28ed14610c773cc9825"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-win32.whl", hash = "sha256:fce3152f94afcfd12f3dd8cf51e48fa606e3cb56719bccebe3b401f43d0714f9"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:37d3c653af15cd88592633e942f5407cb4c64184efab163c40fcebad05f25141"},
|
||||
{file = "rapidfuzz-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:cc594bbcd3c62f647dfac66800f307beaee56b22aaba1c005e9c4c40ed733923"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dea2d113e260a5da0c4003e0a5e9fdf24a9dc2bb9eaa43abd030a1e46ce7837d"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e6c31a4aa68cfa75d7eede8b0ed24b9e458447db604c2db53f358be9843d81d3"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02821366d928e68ddcb567fed8723dad7ea3a979fada6283e6914d5858674850"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe8df315ab4e6db4e1be72c5170f8e66021acde22cd2f9d04d2058a9fd8162e"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:769f31c60cd79420188fcdb3c823227fc4a6deb35cafec9d14045c7f6743acae"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54fa03062124e73086dae66a3451c553c1e20a39c077fd704dc7154092c34c63"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:834d1e818005ed0d4ae38f6b87b86fad9b0a74085467ece0727d20e15077c094"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:948b00e8476a91f510dd1ec07272efc7d78c275d83b630455559671d4e33b678"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-win32.whl", hash = "sha256:43d0305c36f504232f18ea04e55f2059bb89f169d3119c4ea96a0e15b59e2a91"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:ef6bf930b947bd0735c550683939a032090f1d688dfd8861d6b45307b96fd5c5"},
|
||||
{file = "rapidfuzz-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:f3eb0ff3b75d6fdccd40b55e7414bb859a1cda77c52762c9c82b85569f5088e7"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:685c93ea961d135893b5984a5a9851637d23767feabe414ec974f43babbd8226"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa7c8f26f009f8c673fbfb443792f0cf8cf50c4e18121ff1e285b5e08a94fbdb"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57f878330c8d361b2ce76cebb8e3e1dc827293b6abf404e67d53260d27b5d941"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c5f545f454871e6af05753a0172849c82feaf0f521c5ca62ba09e1b382d6382"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:07aa0b5d8863e3151e05026a28e0d924accf0a7a3b605da978f0359bb804df43"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73b07566bc7e010e7b5bd490fb04bb312e820970180df6b5655e9e6224c137db"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6de00eb84c71476af7d3110cf25d8fe7c792d7f5fa86764ef0b4ca97e78ca3ed"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d7843a1abf0091773a530636fdd2a49a41bcae22f9910b86b4f903e76ddc82dc"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-win32.whl", hash = "sha256:dea97ac3ca18cd3ba8f3d04b5c1fe4aa60e58e8d9b7793d3bd595fdb04128d7a"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:b5100fd6bcee4d27f28f4e0a1c6b5127bc8ba7c2a9959cad9eab0bf4a7ab3329"},
|
||||
{file = "rapidfuzz-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:4e49c9e992bc5fc873bd0fff7ef16a4405130ec42f2ce3d2b735ba5d3d4eb70f"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbcb726064b12f356bf10fffdb6db4b6dce5390b23627c08652b3f6e49aa56ae"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1704fc70d214294e554a2421b473779bcdeef715881c5e927dc0f11e1692a0ff"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc65e72790ddfd310c2c8912b45106e3800fefe160b0c2ef4d6b6fec4e826457"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43e38c1305cffae8472572a0584d4ffc2f130865586a81038ca3965301f7c97c"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:e195a77d06c03c98b3fc06b8a28576ba824392ce40de8c708f96ce04849a052e"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b7ef2f4b8583a744338a18f12c69693c194fb6777c0e9ada98cd4d9e8f09d10"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a2135b138bcdcb4c3742d417f215ac2d8c2b87bde15b0feede231ae95f09ec41"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33a325ed0e8e1aa20c3e75f8ab057a7b248fdea7843c2a19ade0008906c14af0"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-win32.whl", hash = "sha256:8383b6d0d92f6cd008f3c9216535be215a064b2cc890398a678b56e6d280cb63"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:e6b5e3036976f0fde888687d91be86d81f9ac5f7b02e218913c38285b756be6c"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:7ba009977601d8b0828bfac9a110b195b3e4e79b350dcfa48c11269a9f1918a0"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0a28add871425c2fe94358c6300bbeb0bc2ed828ca003420ac6825408f5a424"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010e12e2411a4854b0434f920e72b717c43f8ec48d57e7affe5c42ecfa05dd0e"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cfc3d57abd83c734d1714ec39c88a34dd69c85474918ebc21296f1e61eb5ca8"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89acb8cbb52904f763e5ac238083b9fc193bed8d1f03c80568b20e4cef43a519"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-manylinux_2_31_armv7l.whl", hash = "sha256:7d9af908c2f371bfb9c985bd134e295038e3031e666e4b2ade1e7cb7f5af2f1a"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1f1925619627f8798f8c3a391d81071336942e5fe8467bc3c567f982e7ce2897"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:152555187360978119e98ce3e8263d70dd0c40c7541193fc302e9b7125cf8f58"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52619d25a09546b8db078981ca88939d72caa6b8701edd8b22e16482a38e799f"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-win32.whl", hash = "sha256:489ce98a895c98cad284f0a47960c3e264c724cb4cfd47a1430fa091c0c25204"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-win_amd64.whl", hash = "sha256:656e52b054d5b5c2524169240e50cfa080b04b1c613c5f90a2465e84888d6f15"},
|
||||
{file = "rapidfuzz-3.14.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c7e40c0a0af02ad6e57e89f62bef8604f55a04ecae90b0ceeda591bbf5923317"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:442125473b247227d3f2de807a11da6c08ccf536572d1be943f8e262bae7e4ea"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ec0c8c0c3d4f97ced46b2e191e883f8c82dbbf6d5ebc1842366d7eff13cd5a6"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dc37bc20272f388b8c3a4eba4febc6e77e50a8f450c472def4751e7678f55e4"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee362e7e79bae940a5e2b3f6d09c6554db6a4e301cc68343886c08be99844f1"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:4b39921df948388a863f0e267edf2c36302983459b021ab928d4b801cbe6a421"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:beda6aa9bc44d1d81242e7b291b446be352d3451f8217fcb068fc2933927d53b"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6a014ba09657abfcfeed64b7d09407acb29af436d7fc075b23a298a7e4a6b41c"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:32eeafa3abce138bb725550c0e228fc7eaeec7059aa8093d9cbbec2b58c2371a"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-win32.whl", hash = "sha256:adb44d996fc610c7da8c5048775b21db60dd63b1548f078e95858c05c86876a3"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:f3d15d8527e2b293e38ce6e437631af0708df29eafd7c9fc48210854c94472f9"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:576e4b9012a67e0bf54fccb69a7b6c94d4e86a9540a62f1a5144977359133583"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cec3c0da88562727dd5a5a364bd9efeb535400ff0bfb1443156dd139a1dd7b50"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d1fa009f8b1100e4880868137e7bf0501422898f7674f2adcd85d5a67f041296"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b86daa7419b5e8b180690efd1fdbac43ff19230803282521c5b5a9c83977655"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7bd1816db05d6c5ffb3a4df0a2b7b56fb8c81ef584d08e37058afa217da91b1"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:33da4bbaf44e9755b0ce192597f3bde7372fe2e381ab305f41b707a95ac57aa7"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3fecce764cf5a991ee2195a844196da840aba72029b2612f95ac68a8b74946bf"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ecd7453e02cf072258c3a6b8e930230d789d5d46cc849503729f9ce475d0e785"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea188aa00e9bcae8c8411f006a5f2f06c4607a02f24eab0d8dc58566aa911f35"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:7ccbf68100c170e9a0581accbe9291850936711548c6688ce3bfb897b8c589ad"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9ec02e62ae765a318d6de38df609c57fc6dacc65c0ed1fd489036834fd8a620c"},
|
||||
{file = "rapidfuzz-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:e805e52322ae29aa945baf7168b6c898120fbc16d2b8f940b658a5e9e3999253"},
|
||||
{file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7cf174b52cb3ef5d49e45d0a1133b7e7d0ecf770ed01f97ae9962c5c91d97d23"},
|
||||
{file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:442cba39957a008dfc5bdef21a9c3f4379e30ffb4e41b8555dbaf4887eca9300"},
|
||||
{file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1faa0f8f76ba75fd7b142c984947c280ef6558b5067af2ae9b8729b0a0f99ede"},
|
||||
{file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e6eefec45625c634926a9fd46c9e4f31118ac8f3156fff9494422cee45207e6"},
|
||||
{file = "rapidfuzz-3.14.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56fefb4382bb12250f164250240b9dd7772e41c5c8ae976fd598a32292449cc5"},
|
||||
{file = "rapidfuzz-3.14.3.tar.gz", hash = "sha256:2491937177868bc4b1e469087601d53f925e8d270ccc21e07404b4b5814b7b5f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -3340,14 +3334,14 @@ requests = ">=2.0.1,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "safeuploads"
|
||||
version = "0.1.0"
|
||||
version = "0.1.2"
|
||||
description = "A comprehensive file security system for validating uploads and preventing attacks"
|
||||
optional = false
|
||||
python-versions = ">=3.13"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "safeuploads-0.1.0-py3-none-any.whl", hash = "sha256:c1a64b1e3def7c5b84ba6f0ad38dc8d19969438d054d866ba636aa9d6e02441b"},
|
||||
{file = "safeuploads-0.1.0.tar.gz", hash = "sha256:84d56245af9c24ee1b9d380e56cf05d7f88c30b70bbcae2d893de8e3e87c8540"},
|
||||
{file = "safeuploads-0.1.2-py3-none-any.whl", hash = "sha256:a7cc4663eed4af0bbc08e94b2c509f415a51c710613bf37e290c1abd9696d17c"},
|
||||
{file = "safeuploads-0.1.2.tar.gz", hash = "sha256:9275510f6e7fa1d0f09ff51b30700a54a8c4a5746aca7f1d8136ccb3d79711c8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
||||
308
frontend/app/package-lock.json
generated
308
frontend/app/package-lock.json
generated
@@ -72,9 +72,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "6.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.3.tgz",
|
||||
"integrity": "sha512-kiGFeY+Hxf5KbPpjRLf+ffWbkos1aGo8MBfd91oxS3O57RgU3XhZrt/6UzoVF9VMpWbC3v87SRc9jxGrc9qHtQ==",
|
||||
"version": "6.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz",
|
||||
"integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1808,9 +1808,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz",
|
||||
"integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -1825,9 +1825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz",
|
||||
"integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1842,9 +1842,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1859,9 +1859,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1876,9 +1876,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1893,9 +1893,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1910,9 +1910,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1927,9 +1927,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1944,9 +1944,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz",
|
||||
"integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
|
||||
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1961,9 +1961,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1978,9 +1978,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz",
|
||||
"integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1995,9 +1995,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz",
|
||||
"integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
|
||||
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -2012,9 +2012,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz",
|
||||
"integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
|
||||
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -2029,9 +2029,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz",
|
||||
"integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
|
||||
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2046,9 +2046,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz",
|
||||
"integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
|
||||
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2063,9 +2063,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz",
|
||||
"integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
|
||||
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2080,9 +2080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2097,9 +2097,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2114,9 +2114,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2131,9 +2131,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2148,9 +2148,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2165,9 +2165,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2182,9 +2182,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2199,9 +2199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz",
|
||||
"integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
|
||||
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2216,9 +2216,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz",
|
||||
"integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
|
||||
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2233,9 +2233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz",
|
||||
"integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
|
||||
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2330,7 +2330,7 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers/node_modules/@eslint/core": {
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
||||
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
||||
@@ -2343,19 +2343,6 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz",
|
||||
"integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
||||
@@ -2415,9 +2402,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
|
||||
"integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
|
||||
"version": "9.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz",
|
||||
"integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2451,19 +2438,6 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
||||
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
|
||||
@@ -3305,9 +3279,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz",
|
||||
"integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==",
|
||||
"version": "24.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz",
|
||||
"integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3876,9 +3850,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.2.tgz",
|
||||
"integrity": "sha512-PyFDCqpdfYUT+oMLqcc61oHfJlC6yjhybaefwQjRdkchIihToOEpJ2Wu/Ebq2yrnJdd1EsaAvZaXVAqcxtnDxQ==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.1.3.tgz",
|
||||
"integrity": "sha512-KpR1F/eGAG9D1RZ0/T6zWJs6dh/pRLfY5WupecyYKJ1fjVmDMgTPw9wXmKv2rBjo4zCJiOSiyB8BDP1OUwpMEA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4053,9 +4027,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.0.3.tgz",
|
||||
"integrity": "sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.0.5.tgz",
|
||||
"integrity": "sha512-+2bRQFO1f9GLeIabDQWJlluL1NspZlLjpjaSSwwpl+9Tz5tS/3KrceHdwjNvIMEbYWSpoqtOPuXLTSoPgvIEWw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4247,9 +4221,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.22",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.22.tgz",
|
||||
"integrity": "sha512-/tk9kky/d8T8CTXIQYASLyhAxR5VwL3zct1oAoVTaOUHwrmsGnfbRwNdEq+vOl2BN8i3PcDdP0o4Q+jjKQoFbQ==",
|
||||
"version": "2.8.23",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz",
|
||||
"integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -4426,9 +4400,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001752",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz",
|
||||
"integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==",
|
||||
"version": "1.0.30001753",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz",
|
||||
"integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5051,9 +5025,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.11",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz",
|
||||
"integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==",
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -5064,32 +5038,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.25.11",
|
||||
"@esbuild/android-arm": "0.25.11",
|
||||
"@esbuild/android-arm64": "0.25.11",
|
||||
"@esbuild/android-x64": "0.25.11",
|
||||
"@esbuild/darwin-arm64": "0.25.11",
|
||||
"@esbuild/darwin-x64": "0.25.11",
|
||||
"@esbuild/freebsd-arm64": "0.25.11",
|
||||
"@esbuild/freebsd-x64": "0.25.11",
|
||||
"@esbuild/linux-arm": "0.25.11",
|
||||
"@esbuild/linux-arm64": "0.25.11",
|
||||
"@esbuild/linux-ia32": "0.25.11",
|
||||
"@esbuild/linux-loong64": "0.25.11",
|
||||
"@esbuild/linux-mips64el": "0.25.11",
|
||||
"@esbuild/linux-ppc64": "0.25.11",
|
||||
"@esbuild/linux-riscv64": "0.25.11",
|
||||
"@esbuild/linux-s390x": "0.25.11",
|
||||
"@esbuild/linux-x64": "0.25.11",
|
||||
"@esbuild/netbsd-arm64": "0.25.11",
|
||||
"@esbuild/netbsd-x64": "0.25.11",
|
||||
"@esbuild/openbsd-arm64": "0.25.11",
|
||||
"@esbuild/openbsd-x64": "0.25.11",
|
||||
"@esbuild/openharmony-arm64": "0.25.11",
|
||||
"@esbuild/sunos-x64": "0.25.11",
|
||||
"@esbuild/win32-arm64": "0.25.11",
|
||||
"@esbuild/win32-ia32": "0.25.11",
|
||||
"@esbuild/win32-x64": "0.25.11"
|
||||
"@esbuild/aix-ppc64": "0.25.12",
|
||||
"@esbuild/android-arm": "0.25.12",
|
||||
"@esbuild/android-arm64": "0.25.12",
|
||||
"@esbuild/android-x64": "0.25.12",
|
||||
"@esbuild/darwin-arm64": "0.25.12",
|
||||
"@esbuild/darwin-x64": "0.25.12",
|
||||
"@esbuild/freebsd-arm64": "0.25.12",
|
||||
"@esbuild/freebsd-x64": "0.25.12",
|
||||
"@esbuild/linux-arm": "0.25.12",
|
||||
"@esbuild/linux-arm64": "0.25.12",
|
||||
"@esbuild/linux-ia32": "0.25.12",
|
||||
"@esbuild/linux-loong64": "0.25.12",
|
||||
"@esbuild/linux-mips64el": "0.25.12",
|
||||
"@esbuild/linux-ppc64": "0.25.12",
|
||||
"@esbuild/linux-riscv64": "0.25.12",
|
||||
"@esbuild/linux-s390x": "0.25.12",
|
||||
"@esbuild/linux-x64": "0.25.12",
|
||||
"@esbuild/netbsd-arm64": "0.25.12",
|
||||
"@esbuild/netbsd-x64": "0.25.12",
|
||||
"@esbuild/openbsd-arm64": "0.25.12",
|
||||
"@esbuild/openbsd-x64": "0.25.12",
|
||||
"@esbuild/openharmony-arm64": "0.25.12",
|
||||
"@esbuild/sunos-x64": "0.25.12",
|
||||
"@esbuild/win32-arm64": "0.25.12",
|
||||
"@esbuild/win32-ia32": "0.25.12",
|
||||
"@esbuild/win32-x64": "0.25.12"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -5116,20 +5090,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.38.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz",
|
||||
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
|
||||
"version": "9.39.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz",
|
||||
"integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.21.1",
|
||||
"@eslint/config-helpers": "^0.4.1",
|
||||
"@eslint/core": "^0.16.0",
|
||||
"@eslint/config-helpers": "^0.4.2",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.38.0",
|
||||
"@eslint/plugin-kit": "^0.4.0",
|
||||
"@eslint/js": "9.39.0",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
@@ -9461,14 +9435,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue-tsc": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.2.tgz",
|
||||
"integrity": "sha512-3fd4DY0rFczs5f+VB3OhcLU83V6+3Puj2yLBe0Ak65k7ERk+STVNKaOAi0EBo6Lc15UiJB6LzU6Mxy4+h/pKew==",
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.1.3.tgz",
|
||||
"integrity": "sha512-StMNfZHwPIXQgY3KxPKM0Jsoc8b46mDV3Fn2UlHCBIwRJApjqrSwqeMYgWf0zpN+g857y74pv7GWuBm+UqQe1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.23",
|
||||
"@vue/language-core": "3.1.2"
|
||||
"@vue/language-core": "3.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"vue-tsc": "bin/vue-tsc.js"
|
||||
@@ -10245,4 +10219,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,15 +52,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponent
|
||||
*
|
||||
* Generic reusable modal component with configurable action button types.
|
||||
* Supports custom actions with optional value emission.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -68,10 +59,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
modalId: {
|
||||
type: String,
|
||||
@@ -108,25 +95,10 @@ const emit = defineEmits<{
|
||||
submitAction: [value: number | string | boolean | null]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Composables & State
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
|
||||
// ============================================================================
|
||||
// Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle submit action and emit appropriate value
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
if (props.emitValue) {
|
||||
emit('submitAction', props.valueToEmit)
|
||||
@@ -135,20 +107,10 @@ const submitAction = (): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -75,19 +75,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentDateRangeInput
|
||||
*
|
||||
* Reusable modal component for selecting a date range with start and end dates.
|
||||
* Defaults to last 7 days when mounted. Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -95,10 +82,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
interface DateRange {
|
||||
startDate: string
|
||||
endDate: string
|
||||
@@ -128,27 +111,12 @@ const emit = defineEmits<{
|
||||
datesToEmitAction: [dateRange: DateRange]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const startDate = ref('')
|
||||
const endDate = ref('')
|
||||
|
||||
// ============================================================================
|
||||
// Section 7: Validation Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Set default date range to last 7 days
|
||||
*/
|
||||
const setDefaultDates = (): void => {
|
||||
const today = new Date()
|
||||
const sevenDaysAgo = new Date(today)
|
||||
@@ -159,13 +127,6 @@ const setDefaultDates = (): void => {
|
||||
endDate.value = today.toISOString().split('T')[0] || ''
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Emit selected date range to parent component
|
||||
*/
|
||||
const emitDates = (): void => {
|
||||
emit('datesToEmitAction', {
|
||||
startDate: startDate.value,
|
||||
@@ -173,21 +134,11 @@ const emitDates = (): void => {
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal and set default dates on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
setDefaultDates()
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -74,25 +74,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentEmailInput
|
||||
*
|
||||
* Reusable modal component for email input with RFC 5322 compliant validation.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* Features:
|
||||
* - Real-time email validation feedback
|
||||
* - Loading state support
|
||||
* - Input sanitization
|
||||
* - Customizable button types (success, danger, warning, primary)
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, computed, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -102,10 +83,6 @@ import type { ActionButtonType } from '@/types'
|
||||
// Utils
|
||||
import { isValidEmail, sanitizeInput } from '@/utils/validationUtils'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** Unique identifier for the modal element */
|
||||
@@ -136,27 +113,11 @@ const emit = defineEmits<{
|
||||
emailToEmitAction: [email: string]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const emailToEmit = ref(props.emailDefaultValue)
|
||||
|
||||
// ============================================================================
|
||||
// Section 5: Computed Properties
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validate email format using RFC 5322 compliant validation
|
||||
* Returns true if email is valid or empty (to avoid showing error on load)
|
||||
*/
|
||||
const isEmailValid = computed(() => {
|
||||
// Don't show validation error for empty input
|
||||
if (!emailToEmit.value) return true
|
||||
@@ -164,15 +125,6 @@ const isEmailValid = computed(() => {
|
||||
return isValidEmail(emailToEmit.value)
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle form submission
|
||||
* Validates and sanitizes email before emitting to parent
|
||||
* Only emits if email is non-empty and valid
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
if (!emailToEmit.value) return
|
||||
|
||||
@@ -185,20 +137,10 @@ const submitAction = (): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -82,20 +82,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentMFASetup
|
||||
*
|
||||
* Reusable modal component for MFA (Multi-Factor Authentication) setup.
|
||||
* Displays QR code, secret key, and verification code input.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -103,10 +89,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
modalId: {
|
||||
type: String,
|
||||
@@ -167,65 +149,30 @@ const emit = defineEmits<{
|
||||
submitAction: [verificationCode: string]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { modalInstance, initializeModal, showModal, hideModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const verificationCode = ref('')
|
||||
|
||||
// ============================================================================
|
||||
// Section 6: UI Interaction Handlers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Reset verification code when modal is hidden
|
||||
*/
|
||||
const handleModalHidden = (): void => {
|
||||
verificationCode.value = ''
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle form submission and emit verification code
|
||||
*/
|
||||
const handleSubmit = (): void => {
|
||||
if (verificationCode.value) {
|
||||
emit('submitAction', verificationCode.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the modal (exposed for parent component)
|
||||
*/
|
||||
const show = (): void => {
|
||||
showModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the modal (exposed for parent component)
|
||||
*/
|
||||
const hide = (): void => {
|
||||
hideModal()
|
||||
verificationCode.value = ''
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
|
||||
@@ -235,9 +182,6 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
if (modalRef.value) {
|
||||
modalRef.value.removeEventListener('hidden.bs.modal', handleModalHidden)
|
||||
@@ -245,10 +189,6 @@ onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Section 10: Component Definition (Expose methods)
|
||||
// ============================================================================
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
hide
|
||||
|
||||
@@ -83,19 +83,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentNumberAndStringInput
|
||||
*
|
||||
* Reusable modal component for combined numeric and text input with configurable action button types.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -103,10 +90,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
interface FieldsEmitPayload {
|
||||
numberToEmit: number
|
||||
stringToEmit: string
|
||||
@@ -152,27 +135,12 @@ const emit = defineEmits<{
|
||||
fieldsToEmitAction: [payload: FieldsEmitPayload]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const numberToEmit = ref(props.numberDefaultValue)
|
||||
const stringToEmit = ref(props.stringDefaultValue)
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle submit action and emit both field values
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
emit('fieldsToEmitAction', {
|
||||
numberToEmit: numberToEmit.value,
|
||||
@@ -180,20 +148,10 @@ const submitAction = (): void => {
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -65,19 +65,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentNumberInput
|
||||
*
|
||||
* Reusable modal component for numeric input with configurable action button types.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, computed, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -85,10 +72,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
modalId: {
|
||||
type: String,
|
||||
@@ -121,55 +104,23 @@ const emit = defineEmits<{
|
||||
numberToEmitAction: [value: number]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const numberToEmit = ref<number | null>(props.numberDefaultValue)
|
||||
|
||||
// ============================================================================
|
||||
// Section 7: Validation Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if the input has a valid numeric value
|
||||
*/
|
||||
const isValid = computed(() => numberToEmit.value !== null && numberToEmit.value !== undefined)
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle submit action and emit the numeric value
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
if (isValid.value && numberToEmit.value !== null && numberToEmit.value !== undefined) {
|
||||
emit('numberToEmitAction', numberToEmit.value)
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -66,19 +66,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentSelectInput
|
||||
*
|
||||
* Reusable modal component for dropdown/select input with configurable action button types.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -86,10 +73,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
interface SelectOption {
|
||||
id: number
|
||||
name: string
|
||||
@@ -131,44 +114,19 @@ const emit = defineEmits<{
|
||||
optionToEmitAction: [value: number]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const optionToEmit = ref(props.selectCurrentOption)
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle submit action and emit the selected option value
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
emit('optionToEmitAction', optionToEmit.value)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -65,19 +65,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* ModalComponentUploadFile
|
||||
*
|
||||
* Reusable modal component for file upload with configurable accepted file types.
|
||||
* Follows the same structure and patterns as ModalComponent.vue.
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Section 1: Imports
|
||||
// ============================================================================
|
||||
|
||||
// Vue composition API
|
||||
import { ref, onMounted, onUnmounted, type PropType } from 'vue'
|
||||
// Composables
|
||||
@@ -85,10 +72,6 @@ import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { ActionButtonType } from '@/types'
|
||||
|
||||
// ============================================================================
|
||||
// Section 2: Props & Emits
|
||||
// ============================================================================
|
||||
|
||||
const props = defineProps({
|
||||
modalId: {
|
||||
type: String,
|
||||
@@ -121,27 +104,12 @@ const emit = defineEmits<{
|
||||
fileToEmitAction: [file: File]
|
||||
}>()
|
||||
|
||||
// ============================================================================
|
||||
// Section 3: Composables & Stores
|
||||
// ============================================================================
|
||||
|
||||
const { initializeModal, disposeModal } = useBootstrapModal()
|
||||
|
||||
// ============================================================================
|
||||
// Section 4: Reactive State
|
||||
// ============================================================================
|
||||
|
||||
const modalRef = ref<HTMLDivElement | null>(null)
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
const selectedFile = ref<File | null>(null)
|
||||
|
||||
// ============================================================================
|
||||
// Section 6: UI Interaction Handlers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle file input change event
|
||||
*/
|
||||
const handleFileChange = (event: Event): void => {
|
||||
const target = event.target as HTMLInputElement
|
||||
const file = target.files?.[0]
|
||||
@@ -150,14 +118,6 @@ const handleFileChange = (event: Event): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 8: Main Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle submit action and emit the selected file
|
||||
* Clears the file input after emission
|
||||
*/
|
||||
const submitAction = (): void => {
|
||||
if (selectedFile.value) {
|
||||
emit('fileToEmitAction', selectedFile.value)
|
||||
@@ -170,20 +130,10 @@ const submitAction = (): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Section 9: Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Initialize modal on mount
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await initializeModal(modalRef)
|
||||
})
|
||||
|
||||
/**
|
||||
* Clean up modal on unmount
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
})
|
||||
|
||||
@@ -1,48 +1,33 @@
|
||||
/**
|
||||
* Bootstrap Modal Composable
|
||||
*
|
||||
* Provides a type-safe wrapper for Bootstrap Modal component lifecycle management.
|
||||
* Handles initialization, show/hide operations, and cleanup.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { modalInstance, initializeModal, showModal, hideModal } = useBootstrapModal()
|
||||
*
|
||||
* // In component setup
|
||||
* const modalRef = ref(null)
|
||||
* await initializeModal(modalRef)
|
||||
*
|
||||
* // Show/hide modal
|
||||
* showModal()
|
||||
* hideModal()
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { ref, nextTick, type Ref } from 'vue'
|
||||
import { Modal } from 'bootstrap'
|
||||
|
||||
/**
|
||||
* Modal instance reference type
|
||||
* Type representing a Bootstrap Modal instance or null.
|
||||
*/
|
||||
export type BootstrapModalInstance = Modal | null
|
||||
|
||||
/**
|
||||
* Bootstrap Modal composable hook
|
||||
* Composable for managing Bootstrap 5 modal lifecycle and cleanup.
|
||||
*
|
||||
* @returns Modal management functions and state
|
||||
* @returns Object containing modal instance, initialization state, and control methods.
|
||||
*
|
||||
* @remarks
|
||||
* Provides centralized modal management with proper cleanup of Bootstrap's
|
||||
* body styles and backdrops when modals are closed.
|
||||
*/
|
||||
export function useBootstrapModal() {
|
||||
const modalInstance: Ref<BootstrapModalInstance> = ref(null)
|
||||
const isInitialized: Ref<boolean> = ref(false)
|
||||
|
||||
/**
|
||||
* Initialize the Bootstrap Modal instance
|
||||
* Initializes the Bootstrap modal instance from a Vue ref.
|
||||
*
|
||||
* Supports both component refs (with $el property) and template refs (direct DOM elements)
|
||||
* @param modalRef - Vue ref containing the modal element or component.
|
||||
* @returns Promise that resolves when initialization is complete.
|
||||
*
|
||||
* @param modalRef - Vue ref containing either a component instance or HTMLElement
|
||||
* @returns Promise that resolves when modal is initialized
|
||||
* @throws Error if modal element is not found or initialization fails
|
||||
* @remarks
|
||||
* Handles both component refs (`modalRef.value.$el`) and template refs (`modalRef.value`).
|
||||
* Sets up automatic cleanup on modal hide event.
|
||||
*/
|
||||
const initializeModal = async (modalRef: Ref<any>): Promise<void> => {
|
||||
await nextTick()
|
||||
@@ -70,7 +55,11 @@ export function useBootstrapModal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up body styles and attributes left by Bootstrap
|
||||
* Cleans up Bootstrap-applied body styles and backdrops.
|
||||
*
|
||||
* @remarks
|
||||
* Only performs cleanup if no other modals are currently open.
|
||||
* Removes modal-related classes, inline styles, and any remaining backdrop elements.
|
||||
*/
|
||||
const cleanupBodyStyles = (): void => {
|
||||
// Check if any other modals are still open
|
||||
@@ -90,9 +79,10 @@ export function useBootstrapModal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the modal
|
||||
* Shows the modal.
|
||||
*
|
||||
* @throws Error if modal is not initialized
|
||||
* @remarks
|
||||
* Logs a warning if the modal is not initialized.
|
||||
*/
|
||||
const showModal = (): void => {
|
||||
if (!isInitialized.value || !modalInstance.value) {
|
||||
@@ -103,9 +93,11 @@ export function useBootstrapModal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the modal and clean up backdrop
|
||||
* Hides the modal.
|
||||
*
|
||||
* @throws Error if modal is not initialized
|
||||
* @remarks
|
||||
* Logs a warning if the modal is not initialized.
|
||||
* Cleanup is automatically handled by the `hidden.bs.modal` event listener.
|
||||
*/
|
||||
const hideModal = (): void => {
|
||||
if (!isInitialized.value || !modalInstance.value) {
|
||||
@@ -117,7 +109,11 @@ export function useBootstrapModal() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of the modal instance and clean up resources
|
||||
* Disposes of the modal instance and performs cleanup.
|
||||
*
|
||||
* @remarks
|
||||
* Calls Bootstrap's `dispose()` method, cleans up body styles,
|
||||
* and resets the modal instance and initialization state.
|
||||
*/
|
||||
const disposeModal = (): void => {
|
||||
if (modalInstance.value) {
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* Gear Avatar Constants
|
||||
*
|
||||
* Centralized constants for gear type avatar images.
|
||||
* Maps gear types to their corresponding avatar images.
|
||||
*/
|
||||
|
||||
import bicycle1 from '@/assets/avatar/bicycle1.png'
|
||||
import runningShoe1 from '@/assets/avatar/running_shoe1.png'
|
||||
import wetsuit1 from '@/assets/avatar/wetsuit1.png'
|
||||
@@ -15,8 +8,18 @@ import windsurf1 from '@/assets/avatar/windsurf1.png'
|
||||
import waterSportsBoard1 from '@/assets/avatar/waterSportsBoard1.png'
|
||||
|
||||
/**
|
||||
* Gear type avatar mapping
|
||||
* Maps gear type IDs to their avatar image paths
|
||||
* Maps gear type IDs to their corresponding avatar image paths.
|
||||
*
|
||||
* @remarks
|
||||
* The mapping includes:
|
||||
* - 1: Bicycle
|
||||
* - 2: Running shoe
|
||||
* - 3: Wetsuit
|
||||
* - 4: Racquet
|
||||
* - 5: Skis
|
||||
* - 6: Snowboard
|
||||
* - 7: Windsurf
|
||||
* - 8: Water sports board
|
||||
*/
|
||||
export const GEAR_AVATAR_MAP: Record<number, string> = {
|
||||
1: bicycle1,
|
||||
@@ -30,9 +33,10 @@ export const GEAR_AVATAR_MAP: Record<number, string> = {
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Get avatar image for a specific gear type
|
||||
* @param gearType - The gear type ID (1-8)
|
||||
* @returns The avatar image path
|
||||
* Retrieves the avatar image path for a given gear type.
|
||||
*
|
||||
* @param gearType - The numeric identifier for the gear type.
|
||||
* @returns The image path for the specified gear type, or the default bicycle avatar if not found.
|
||||
*/
|
||||
export function getGearAvatar(gearType: number): string {
|
||||
return GEAR_AVATAR_MAP[gearType] ?? bicycle1
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
/**
|
||||
* HTTP Status Codes and Error Handling Utilities
|
||||
* Standard HTTP status codes used throughout the application.
|
||||
*
|
||||
* Centralized constants and utilities for HTTP status codes and error handling
|
||||
* across the Endurain application.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Common HTTP status codes used throughout the application
|
||||
* @remarks
|
||||
* Includes common success (2xx), client error (4xx), and server error (5xx) status codes.
|
||||
*/
|
||||
export const HTTP_STATUS = {
|
||||
OK: 200,
|
||||
@@ -22,20 +18,27 @@ export const HTTP_STATUS = {
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Type for HTTP status code values
|
||||
* Type representing valid HTTP status code values from the `HTTP_STATUS` constant.
|
||||
*/
|
||||
export type HttpStatusCode = (typeof HTTP_STATUS)[keyof typeof HTTP_STATUS]
|
||||
|
||||
/**
|
||||
* Common query parameter constant for boolean 'true' values
|
||||
* Constant string value for boolean query parameters.
|
||||
*
|
||||
* @remarks
|
||||
* Use this for URL query parameters that represent `true` values.
|
||||
*/
|
||||
export const QUERY_PARAM_TRUE = 'true' as const
|
||||
|
||||
/**
|
||||
* Extract status code from error response or error string
|
||||
* Extracts the HTTP status code from an error object.
|
||||
*
|
||||
* @param error - Error object that may contain a response with status code
|
||||
* @returns The extracted status code or null if not found
|
||||
* @param error - The error object to extract the status code from.
|
||||
* @returns The HTTP status code if found, or `null` if not extractable.
|
||||
*
|
||||
* @remarks
|
||||
* First attempts to extract from `error.response.status`, then falls back to
|
||||
* pattern matching common status codes in the error string.
|
||||
*/
|
||||
export const extractStatusCode = (error: unknown): number | null => {
|
||||
// Try to get status from response object
|
||||
@@ -59,21 +62,30 @@ export const extractStatusCode = (error: unknown): number | null => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if status code is a client error (4xx)
|
||||
* Checks if the status code represents a client error (4xx).
|
||||
*
|
||||
* @param statusCode - The HTTP status code to check.
|
||||
* @returns `true` if the status code is between 400 and 499, `false` otherwise.
|
||||
*/
|
||||
export const isClientError = (statusCode: number): boolean => {
|
||||
return statusCode >= 400 && statusCode < 500
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if status code is a server error (5xx)
|
||||
* Checks if the status code represents a server error (5xx).
|
||||
*
|
||||
* @param statusCode - The HTTP status code to check.
|
||||
* @returns `true` if the status code is between 500 and 599, `false` otherwise.
|
||||
*/
|
||||
export const isServerError = (statusCode: number): boolean => {
|
||||
return statusCode >= 500 && statusCode < 600
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if status code is successful (2xx)
|
||||
* Checks if the status code represents a successful response (2xx).
|
||||
*
|
||||
* @param statusCode - The HTTP status code to check.
|
||||
* @returns `true` if the status code is between 200 and 299, `false` otherwise.
|
||||
*/
|
||||
export const isSuccessStatus = (statusCode: number): boolean => {
|
||||
return statusCode >= 200 && statusCode < 300
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
/**
|
||||
* Integration Logo Constants
|
||||
*
|
||||
* Centralized constants for third-party integration logos.
|
||||
* Maps integration services to their logo images.
|
||||
*/
|
||||
|
||||
import stravaLogo from '@/assets/strava/api_logo_cptblWith_strava_horiz_light.png'
|
||||
import garminConnectBadge from '@/assets/garminconnect/Garmin_connect_badge_print_RESOURCE_FILE-01.png'
|
||||
import garminConnectApp from '@/assets/garminconnect/Garmin_Connect_app_1024x1024-02.png'
|
||||
|
||||
/**
|
||||
* Integration logo mapping
|
||||
* Logo and badge images for third-party fitness integrations.
|
||||
*
|
||||
* @remarks
|
||||
* Contains logos for:
|
||||
* - **strava**: Strava horizontal logo (compatible with light backgrounds)
|
||||
* - **garminConnectBadge**: Garmin Connect badge for print resources
|
||||
* - **garminConnectApp**: Garmin Connect app icon (1024x1024)
|
||||
*/
|
||||
export const INTEGRATION_LOGOS = {
|
||||
strava: stravaLogo,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
/**
|
||||
* SSO Constants
|
||||
*
|
||||
* Centralized constants for Single Sign-On (SSO) functionality
|
||||
* including provider logos and configuration mappings.
|
||||
*/
|
||||
|
||||
import keycloakLogo from '@/assets/sso/keycloak.svg'
|
||||
import authentikLogo from '@/assets/sso/authentik.svg'
|
||||
import autheliaLogo from '@/assets/sso/authelia.svg'
|
||||
|
||||
/**
|
||||
* Custom logo mapping for SSO providers
|
||||
* Maps provider icon names to image paths for custom logos
|
||||
* Maps SSO provider names to their corresponding logo image paths.
|
||||
*
|
||||
* @remarks
|
||||
* Supported SSO providers:
|
||||
* - **keycloak**: Keycloak identity and access management
|
||||
* - **authentik**: Authentik authentication provider
|
||||
* - **authelia**: Authelia authentication and authorization server
|
||||
*/
|
||||
export const PROVIDER_CUSTOM_LOGO_MAP: Record<string, string> = {
|
||||
keycloak: keycloakLogo,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
/**
|
||||
* User Avatar Constants
|
||||
*
|
||||
* Centralized constants for user avatar images by gender.
|
||||
* Maps gender types to their corresponding default avatar images.
|
||||
*/
|
||||
|
||||
import maleAvatar from '@/assets/avatar/male1.png'
|
||||
import femaleAvatar from '@/assets/avatar/female1.png'
|
||||
import unspecifiedAvatar from '@/assets/avatar/unspecified1.png'
|
||||
|
||||
/**
|
||||
* User avatar mapping by gender
|
||||
* Maps gender IDs to their default avatar image paths
|
||||
* Maps gender identifiers to their corresponding default avatar image paths.
|
||||
*
|
||||
* @remarks
|
||||
* Gender mappings:
|
||||
* - **1**: Male avatar
|
||||
* - **2**: Female avatar
|
||||
* - **3**: Unspecified/neutral avatar
|
||||
*/
|
||||
export const USER_AVATAR_MAP: Record<number, string> = {
|
||||
1: maleAvatar,
|
||||
@@ -20,9 +18,14 @@ export const USER_AVATAR_MAP: Record<number, string> = {
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Get default avatar image for a specific gender
|
||||
* @param gender - The gender ID (1=male, 2=female, 3=unspecified)
|
||||
* @returns The default avatar image path
|
||||
* Retrieves the default avatar image path for a given gender.
|
||||
*
|
||||
* @param gender - The numeric gender identifier (1-3). Optional.
|
||||
* @returns The avatar image path corresponding to the gender, or the male avatar as default.
|
||||
*
|
||||
* @remarks
|
||||
* Returns male avatar if gender is undefined, null, or out of range (< 1 or > 3).
|
||||
* Falls back to unspecified avatar if the gender value is not found in the map.
|
||||
*/
|
||||
export function getUserDefaultAvatar(gender?: number): string {
|
||||
if (!gender || gender < 1 || gender > 3) {
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* Identity Providers API Service
|
||||
*
|
||||
* Handles all API calls related to external identity providers (SSO/OAuth/OIDC).
|
||||
*/
|
||||
|
||||
import {
|
||||
fetchGetRequest,
|
||||
fetchPostRequest,
|
||||
@@ -14,75 +8,24 @@ import {
|
||||
import { fetchPublicGetRequest } from '@/utils/servicePublicUtils'
|
||||
|
||||
export const identityProviders = {
|
||||
/**
|
||||
* Get all configured identity providers (admin only)
|
||||
* @returns {Promise} Array of identity providers
|
||||
*/
|
||||
getAllProviders() {
|
||||
return fetchGetRequest('idp')
|
||||
},
|
||||
|
||||
/**
|
||||
* Get list of enabled identity providers for login page (public)
|
||||
* @returns {Promise} Array of public identity provider info
|
||||
*/
|
||||
getEnabledProviders() {
|
||||
return fetchPublicGetRequest('public/idp')
|
||||
},
|
||||
|
||||
/**
|
||||
* Get list of pre-configured IdP templates (admin only)
|
||||
* @returns {Promise} Array of available templates
|
||||
*/
|
||||
getTemplates() {
|
||||
return fetchGetRequest('idp/templates')
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new identity provider (admin only)
|
||||
* @param {Object} data - Identity provider configuration
|
||||
* @param {string} data.name - Display name
|
||||
* @param {string} data.slug - URL-safe identifier
|
||||
* @param {string} data.provider_type - Type (oidc, oauth2, saml)
|
||||
* @param {boolean} data.enabled - Whether enabled
|
||||
* @param {string} data.client_id - OAuth2/OIDC client ID
|
||||
* @param {string} data.client_secret - OAuth2/OIDC client secret
|
||||
* @param {string} [data.issuer_url] - OIDC issuer URL
|
||||
* @param {string} [data.scopes] - OAuth scopes
|
||||
* @param {string} [data.icon] - Icon name or URL
|
||||
* @param {boolean} [data.auto_create_users] - Auto-create users
|
||||
* @param {boolean} [data.sync_user_info] - Sync user info on login
|
||||
* @param {Object} [data.user_mapping] - Claims mapping
|
||||
* @returns {Promise} Created identity provider
|
||||
*/
|
||||
createProvider(data) {
|
||||
return fetchPostRequest('idp', data)
|
||||
},
|
||||
|
||||
/**
|
||||
* Update an existing identity provider (admin only)
|
||||
* @param {number} idpId - The identity provider ID
|
||||
* @param {Object} data - Updated identity provider configuration
|
||||
* @returns {Promise} Updated identity provider
|
||||
*/
|
||||
updateProvider(idpId, data) {
|
||||
return fetchPutRequest(`idp/${idpId}`, data)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete an identity provider (admin only)
|
||||
* @param {number} idpId - The identity provider ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
deleteProvider(idpId) {
|
||||
return fetchDeleteRequest(`idp/${idpId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* Initiate SSO login with a specific provider
|
||||
* Redirects browser to IdP authorization page
|
||||
* @param {string} slug - The provider slug
|
||||
*/
|
||||
initiateLogin(slug) {
|
||||
window.location.href = `${API_URL}public/idp/login/${slug}`
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { fetchGetRequest } from '@/utils/serviceUtils'
|
||||
|
||||
export const summaryService = {
|
||||
/**
|
||||
* Fetches activity summary data for a user based on view type and period.
|
||||
* @param {number} userId - The ID of the user.
|
||||
* @param {string} viewType - The type of summary ('week', 'month', 'year').
|
||||
* @param {object} params - Query parameters (e.g., { date: 'YYYY-MM-DD' } or { year: YYYY }).
|
||||
* @param {string | null} activityType - Optional activity type name to filter by.
|
||||
* @returns {Promise<object>} - The summary data.
|
||||
*/
|
||||
getSummary(userId, viewType, params = {}, activityType = null) {
|
||||
// Added activityType parameter
|
||||
const url = `activities_summaries/${viewType}`
|
||||
|
||||
@@ -1,80 +1,9 @@
|
||||
/**
|
||||
* User Identity Providers Service
|
||||
*
|
||||
* Handles API communication for user-specific identity provider links.
|
||||
* These endpoints are used by administrators to view and manage which external
|
||||
* authentication providers (SSO) are linked to user accounts.
|
||||
*
|
||||
* Security:
|
||||
* - Admin-only operations (backend enforces sessions:read, sessions:write scopes)
|
||||
* - Does not expose refresh tokens (backend security)
|
||||
* - All operations require valid JWT authentication
|
||||
*
|
||||
* Related Files:
|
||||
* - Backend Router: backend/app/users/user_identity_providers/router.py
|
||||
* - TypeScript Types: frontend/app/src/types/index.ts (UserIdentityProviderEnriched)
|
||||
* - Usage: UsersListComponent.vue (admin panel)
|
||||
*/
|
||||
|
||||
import { fetchGetRequest, fetchDeleteRequest } from '@/utils/serviceUtils'
|
||||
|
||||
export const userIdentityProviders = {
|
||||
/**
|
||||
* Get all identity provider links for a specific user
|
||||
*
|
||||
* Retrieves the list of external authentication providers (SSO) that are
|
||||
* linked to the user's account. The response includes IDP metadata such as
|
||||
* provider name, icon, and login history.
|
||||
*
|
||||
* Security:
|
||||
* - Admin-only endpoint (sessions:read scope)
|
||||
* - Refresh tokens are NOT included in response
|
||||
* - Only metadata is exposed
|
||||
*
|
||||
* @param {number} userId - Target user ID
|
||||
* @returns {Promise<UserIdentityProviderEnriched[]>} Array of IDP links with enriched details
|
||||
*
|
||||
* @example
|
||||
* const idps = await userIdentityProviders.getUserIdentityProviders(123)
|
||||
* console.log(idps) // [{ id: 1, idp_name: "Google", ... }]
|
||||
*
|
||||
* @throws {Error} 404 if user not found
|
||||
* @throws {Error} 403 if insufficient permissions (non-admin)
|
||||
* @throws {Error} 401 if not authenticated
|
||||
*/
|
||||
getUserIdentityProviders(userId) {
|
||||
return fetchGetRequest(`users/${userId}/identity-providers`)
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user's identity provider link
|
||||
*
|
||||
* Removes the connection between a user and an external authentication provider.
|
||||
* After deletion, the user will no longer be able to log in using this provider,
|
||||
* but can still use their password (if set) or other linked providers.
|
||||
*
|
||||
* Security:
|
||||
* - Admin-only endpoint (sessions:write scope)
|
||||
* - Audit logging performed on backend
|
||||
* - Clears encrypted refresh tokens before deletion
|
||||
*
|
||||
* Side Effects:
|
||||
* - Deletes user_identity_provider record from database
|
||||
* - Clears stored refresh tokens (defense in depth)
|
||||
* - Creates audit log entry on backend
|
||||
*
|
||||
* @param {number} userId - Target user ID whose IDP link will be deleted
|
||||
* @param {number} idpId - Identity provider ID to unlink from the user
|
||||
* @returns {Promise<void>} Promise that resolves when deletion is complete (204 No Content)
|
||||
*
|
||||
* @example
|
||||
* await userIdentityProviders.deleteUserIdentityProvider(123, 5)
|
||||
* // IDP link deleted, user can no longer log in with that provider
|
||||
*
|
||||
* @throws {Error} 404 if user, IDP, or link not found
|
||||
* @throws {Error} 403 if insufficient permissions (non-admin)
|
||||
* @throws {Error} 401 if not authenticated
|
||||
*/
|
||||
deleteUserIdentityProvider(userId, idpId) {
|
||||
return fetchDeleteRequest(`users/${userId}/identity-providers/${idpId}`)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
/**
|
||||
* Common type definitions for the Endurain application
|
||||
*
|
||||
* This file serves as a starting point for TypeScript type definitions.
|
||||
* As you migrate JavaScript code to TypeScript, add relevant types here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notification Types
|
||||
* Used by the notivue library for displaying different types of notifications
|
||||
* Notification type for the notivue library.
|
||||
*/
|
||||
export type NotificationType = 'warning' | 'success' | 'error' | 'info'
|
||||
|
||||
/**
|
||||
* Action Button Types
|
||||
* Bootstrap button style variants for action buttons in modals and forms
|
||||
* Bootstrap button style variant for action buttons.
|
||||
*/
|
||||
export type ActionButtonType = 'success' | 'danger' | 'warning' | 'primary'
|
||||
|
||||
/**
|
||||
* Error object with optional response property
|
||||
* Common structure for API errors
|
||||
* Error object structure for API errors.
|
||||
*
|
||||
* @property response - Optional response object from the API.
|
||||
* @property response.status - HTTP status code.
|
||||
* @property response.data - Response data payload.
|
||||
* @property message - Optional error message.
|
||||
* @property toString - Converts the error to a string representation.
|
||||
*/
|
||||
export interface ErrorWithResponse {
|
||||
response?: {
|
||||
@@ -31,8 +27,10 @@ export interface ErrorWithResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* Route Query Handler
|
||||
* Configuration for handling route query parameters with notifications
|
||||
* Configuration for handling route query parameters with notifications.
|
||||
*
|
||||
* @property type - The notification type to display.
|
||||
* @property key - The query parameter key name.
|
||||
*/
|
||||
export interface RouteQueryHandler {
|
||||
type: NotificationType
|
||||
@@ -40,16 +38,18 @@ export interface RouteQueryHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Route Query Handlers Map
|
||||
* Map of query parameter names to their handler configurations
|
||||
* Map of query parameter names to their handler configurations.
|
||||
*/
|
||||
export interface RouteQueryHandlers {
|
||||
[key: string]: RouteQueryHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Login Response
|
||||
* Response structure from authentication endpoints
|
||||
* Response structure from authentication endpoints.
|
||||
*
|
||||
* @property mfa_required - Whether MFA verification is required.
|
||||
* @property username - The authenticated username.
|
||||
* @property session_id - The session identifier token.
|
||||
*/
|
||||
export interface LoginResponse {
|
||||
mfa_required?: boolean
|
||||
@@ -58,8 +58,15 @@ export interface LoginResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity Provider Template
|
||||
* Configuration template for identity providers (OIDC, OAuth2, etc.)
|
||||
* Configuration template for identity providers.
|
||||
*
|
||||
* @property name - The display name of the provider.
|
||||
* @property provider_type - The type of provider (OIDC, OAuth2, etc.).
|
||||
* @property issuer_url - The issuer URL for the identity provider.
|
||||
* @property scopes - The OAuth scopes requested.
|
||||
* @property icon - The icon identifier for the provider.
|
||||
* @property description - A description of the provider.
|
||||
* @property configuration_notes - Additional configuration notes.
|
||||
*/
|
||||
export interface IdentityProviderTemplate {
|
||||
name: string
|
||||
@@ -72,9 +79,23 @@ export interface IdentityProviderTemplate {
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity Provider
|
||||
* Represents a configured external identity provider for SSO authentication
|
||||
* Combines properties used for both editing and displaying providers
|
||||
* Configured external identity provider for SSO authentication.
|
||||
*
|
||||
* @property id - Unique identifier for the provider.
|
||||
* @property name - The display name of the provider.
|
||||
* @property slug - URL-friendly identifier for the provider.
|
||||
* @property provider_type - The type of provider (OIDC, OAuth2, etc.).
|
||||
* @property icon - Optional icon identifier for the provider.
|
||||
* @property enabled - Whether the provider is currently enabled.
|
||||
* @property issuer_url - The issuer URL for the identity provider.
|
||||
* @property client_id - The OAuth client ID.
|
||||
* @property client_secret - The OAuth client secret (edit only).
|
||||
* @property scopes - The OAuth scopes requested (edit only).
|
||||
* @property auto_create_users - Whether to automatically create users (edit only).
|
||||
* @property sync_user_info - Whether to sync user information (edit only).
|
||||
* @property authorization_endpoint - The authorization endpoint URL (display only).
|
||||
* @property token_endpoint - The token endpoint URL (display only).
|
||||
* @property userinfo_endpoint - The userinfo endpoint URL (display only).
|
||||
*/
|
||||
export interface IdentityProvider {
|
||||
id: number
|
||||
@@ -97,9 +118,12 @@ export interface IdentityProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* SSO Provider (Public)
|
||||
* Lightweight representation of an identity provider for the login page
|
||||
* Contains only the public information needed for display and authentication
|
||||
* Public identity provider information for the login page.
|
||||
*
|
||||
* @property id - Unique identifier for the provider.
|
||||
* @property name - The display name of the provider.
|
||||
* @property slug - URL-friendly identifier for the provider.
|
||||
* @property icon - Optional icon identifier for the provider.
|
||||
*/
|
||||
export interface SSOProvider {
|
||||
id: number
|
||||
@@ -109,13 +133,19 @@ export interface SSOProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* User Identity Provider Link
|
||||
* Represents a user's connection to an external identity provider (SSO)
|
||||
* Used for displaying and managing user authentication methods in admin panel
|
||||
* User's connection to an external identity provider (SSO).
|
||||
*
|
||||
* Security Note:
|
||||
* - Refresh tokens are NOT included in this interface (handled securely on backend)
|
||||
* - Only metadata and non-sensitive information is exposed
|
||||
* @property id - Unique identifier for the link.
|
||||
* @property user_id - The user's unique identifier.
|
||||
* @property idp_id - The identity provider's unique identifier.
|
||||
* @property idp_subject - The subject identifier from the identity provider.
|
||||
* @property linked_at - ISO 8601 datetime when the link was created.
|
||||
* @property last_login - ISO 8601 datetime of last login, or null if never logged in.
|
||||
* @property idp_access_token_expires_at - ISO 8601 datetime when the access token expires, or null.
|
||||
* @property idp_refresh_token_updated_at - ISO 8601 datetime when the refresh token was last updated, or null.
|
||||
*
|
||||
* @remarks
|
||||
* Refresh tokens are NOT included in this interface and are handled securely on the backend.
|
||||
*/
|
||||
export interface UserIdentityProvider {
|
||||
id: number
|
||||
@@ -129,14 +159,16 @@ export interface UserIdentityProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* User Identity Provider Enriched
|
||||
* Extended version of UserIdentityProvider that includes IDP details for display
|
||||
* The backend enriches responses with IDP information joined from identity_providers table
|
||||
* This is the primary interface used in the admin UI for displaying user SSO connections
|
||||
* Extended user identity provider with IDP details for display.
|
||||
*
|
||||
* Usage:
|
||||
* - UserIdentityProviderListComponent (display individual IDP link)
|
||||
* - UsersListComponent (display user's IDPs in tab)
|
||||
* @property idp_name - The name of the identity provider.
|
||||
* @property idp_slug - URL-friendly identifier for the identity provider.
|
||||
* @property idp_icon - Optional icon identifier for the identity provider.
|
||||
* @property idp_provider_type - The type of the identity provider.
|
||||
*
|
||||
* @remarks
|
||||
* The backend enriches responses with IDP information from the identity_providers table.
|
||||
* Used in UserIdentityProviderListComponent and UsersListComponent.
|
||||
*/
|
||||
export interface UserIdentityProviderEnriched extends UserIdentityProvider {
|
||||
idp_name: string
|
||||
@@ -146,8 +178,10 @@ export interface UserIdentityProviderEnriched extends UserIdentityProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Window Environment Extension
|
||||
* Extends the global Window interface to include custom env property
|
||||
* Global Window interface extension for environment configuration.
|
||||
*
|
||||
* @property env - Environment configuration object.
|
||||
* @property env.ENDURAIN_HOST - The host URL for the Endurain API.
|
||||
*/
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { DateTime } from 'luxon'
|
||||
|
||||
/**
|
||||
* Formats a date string into a localized date format.
|
||||
*
|
||||
* @param {string} dateString - The date string to be formatted.
|
||||
* @returns {string} The formatted date string.
|
||||
*/
|
||||
export function formatDateShort(dateString) {
|
||||
// Create a DateTime object from the date string
|
||||
const date = DateTime.fromISO(dateString, { setZone: true })
|
||||
@@ -22,11 +16,6 @@ export function formatDateMed(dateString) {
|
||||
return date.toLocaleString(DateTime.DATE_MED)
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a given date string into a time string.
|
||||
* @param {string} dateString - The date string to be formatted.
|
||||
* @returns {string} The formatted time string.
|
||||
*/
|
||||
export function formatTime(dateString) {
|
||||
// Create a DateTime object from the date string and preserve its time zone offset
|
||||
const date = DateTime.fromISO(dateString, { setZone: true })
|
||||
@@ -35,13 +24,6 @@ export function formatTime(dateString) {
|
||||
return date.toLocaleString(DateTime.TIME_SIMPLE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the time difference between two given timestamps.
|
||||
*
|
||||
* @param {string} startTime - The start timestamp.
|
||||
* @param {string} endTime - The end timestamp.
|
||||
* @returns {string} The formatted time difference.
|
||||
*/
|
||||
export function calculateTimeDifference(startTime, endTime) {
|
||||
// Create new Date objects from the timestamps
|
||||
const startDateTime = new Date(startTime)
|
||||
@@ -62,12 +44,6 @@ export function calculateTimeDifference(startTime, endTime) {
|
||||
return `${hours}h ${minutes}m`
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given number of seconds into a minutes:seconds format.
|
||||
*
|
||||
* @param {number} totalSeconds - The total number of seconds.
|
||||
* @returns {string} The formatted time string in minutes:seconds.
|
||||
*/
|
||||
export function formatSecondsToMinutes(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
@@ -82,12 +58,6 @@ export function formatSecondsToMinutes(totalSeconds) {
|
||||
return `${minutes}m ${formattedSeconds}s`
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a total number of seconds to the equivalent whole hours.
|
||||
*
|
||||
* @param {number} totalSeconds - The total number of seconds to convert.
|
||||
* @returns {number} The number of whole hours represented by the input seconds. Returns 0 if less than one hour.
|
||||
*/
|
||||
export function formatSecondsToOnlyHours(totalSeconds) {
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
|
||||
@@ -97,21 +67,6 @@ export function formatSecondsToOnlyHours(totalSeconds) {
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start date (Monday) of the week for a given date object, in UTC.
|
||||
* @param {Date} date - The input data object.
|
||||
* @returns {Date} - The data object for the Monday of that week (UTC).
|
||||
|
||||
export function getWeekStartDate(date) {
|
||||
return DateTime.fromJSDate(date, { zone: 'utc' }).startOf('week').toJSDate();
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Gets the start date of the week for a given date object, respecting the specified first day of week, in UTC.
|
||||
* @param {Date} date - The input date object.
|
||||
* @param {number} firstDayOfWeek - The first day of week (0 = Sunday, 1 = Monday, etc.).
|
||||
* @returns {Date} - The date object for the start of that week (UTC).
|
||||
*/
|
||||
export function getWeekStartDate(date, firstDayOfWeek = 0) {
|
||||
const dt = DateTime.fromJSDate(date, { zone: 'utc' })
|
||||
|
||||
@@ -128,45 +83,17 @@ export function getWeekStartDate(date, firstDayOfWeek = 0) {
|
||||
return dt.minus({ days: daysToSubtract }).toJSDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the end date (start of next week) for a given JavaScript Date object's week, in UTC.
|
||||
* This means it's the first day of the next week, making the range exclusive for the end date.
|
||||
* @param {Date} jsDate - The input JavaScript Date object.
|
||||
* @returns {Date} - The JavaScript Date object for the start of the next week (UTC).
|
||||
|
||||
export function getWeekEndDate(jsDate) {
|
||||
return DateTime.fromJSDate(jsDate, { zone: 'utc' }).startOf('week').plus({ days: 7 }).toJSDate();
|
||||
}*/
|
||||
/**
|
||||
* Gets the end date (start of next week) for a given JavaScript Date object's week, in UTC.
|
||||
* This means it's the first day of the next week, making the range exclusive for the end date.
|
||||
* @param {Date} jsDate - The input JavaScript Date object.
|
||||
* @param {number} firstDayOfWeek - The first day of week (0 = Sunday, 1 = Monday, etc.).
|
||||
* @returns {Date} - The JavaScript Date object for the start of the next week (UTC).
|
||||
*/
|
||||
export function getWeekEndDate(jsDate, firstDayOfWeek = 0) {
|
||||
const weekStart = getWeekStartDate(jsDate, firstDayOfWeek)
|
||||
return DateTime.fromJSDate(weekStart, { zone: 'utc' }).plus({ days: 7 }).toJSDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the previous or next week based on the specified first day of week.
|
||||
* @param {Date} currentDate - The current date.
|
||||
* @param {number} direction - Direction to navigate (-1 for previous, 1 for next).
|
||||
* @param {number} firstDayOfWeek - The first day of week (0 = Sunday, 1 = Monday, etc.).
|
||||
* @returns {Date} - The new date after navigation.
|
||||
*/
|
||||
export function navigateWeek(currentDate, direction, firstDayOfWeek = 0) {
|
||||
return DateTime.fromJSDate(currentDate, { zone: 'utc' })
|
||||
.plus({ days: 7 * direction })
|
||||
.toJSDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the start date (1st) of the month for a given JavaScript Date object, in UTC.
|
||||
* @param {Date} jsDate - The input JavaScript Date object.
|
||||
* @returns {Date} - The JavaScript Date object for the first day of that month (UTC).
|
||||
*/
|
||||
export function getMonthStartDate(jsDate) {
|
||||
return DateTime.fromJSDate(jsDate, { zone: 'utc' }).startOf('month').toJSDate()
|
||||
}
|
||||
@@ -184,24 +111,12 @@ export function getMonthEndDate(jsDate) {
|
||||
.toJSDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Date object into a string with the format "YYYY-MM".
|
||||
*
|
||||
* @param {Date} date - The date to format.
|
||||
* @returns {string} The formatted date string in "YYYY-MM" format.
|
||||
*/
|
||||
export function formatDateToMonthString(date) {
|
||||
let year = date.getFullYear()
|
||||
let month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
return `${year}-${month}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a Date object into an ISO date string (YYYY-MM-DD).
|
||||
*
|
||||
* @param {Date} date - The date to format.
|
||||
* @returns {string} The formatted date string in ISO format.
|
||||
*/
|
||||
export function formatDateISO(date) {
|
||||
let year = date.getFullYear()
|
||||
let month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
|
||||
@@ -6,37 +6,6 @@ import {
|
||||
formatDateISO
|
||||
} from '@/utils/dateTimeUtils'
|
||||
|
||||
/**
|
||||
* Builds parameters for summary API call
|
||||
* @param {string} viewType - The selected view type
|
||||
* @param {string} selectedDate - The selected date in ISO format
|
||||
* @param {number} selectedYear - The selected year
|
||||
* @param {number} firstDayOfWeek - The first day of week preference (0 = Sunday, 1 = Monday, etc.)
|
||||
* @returns {Object} - Parameters object for the API call
|
||||
|
||||
export function buildSummaryParams(viewType, selectedDate, selectedYear, firstDayOfWeek = 0) {
|
||||
const params = {};
|
||||
|
||||
if (viewType === "year") {
|
||||
params.year = selectedYear;
|
||||
} else if (viewType === "week" || viewType === "month") {
|
||||
params.date = selectedDate;
|
||||
// Include firstDayOfWeek for week view to ensure backend uses correct week boundaries
|
||||
if (viewType === "week") {
|
||||
params.firstDayOfWeek = firstDayOfWeek;
|
||||
}
|
||||
}
|
||||
// For 'lifetime', params remains empty
|
||||
|
||||
return params;
|
||||
} */
|
||||
/**
|
||||
* @param {string} viewType - The selected view type
|
||||
* @param {string} selectedDate - The selected date in ISO format
|
||||
* @param {number} selectedYear - The selected year
|
||||
* @param {number} firstDayOfWeek - The first day of week preference (0 = Sunday, 1 = Monday, etc.)
|
||||
* @returns {Object} - Parameters object for the API call
|
||||
*/
|
||||
export function buildSummaryParams(viewType, selectedDate, selectedYear, firstDayOfWeek = 0) {
|
||||
const params = {}
|
||||
|
||||
@@ -54,58 +23,6 @@ export function buildSummaryParams(viewType, selectedDate, selectedYear, firstDa
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds filters for activities API call
|
||||
* @param {string} viewType - The selected view type
|
||||
* @param {string} selectedDate - The selected date in ISO format
|
||||
* @param {number} selectedYear - The selected year
|
||||
* @param {string} selectedActivityType - The selected activity type
|
||||
* @param {number} firstDayOfWeek - The first day of week preference (0 = Sunday, 1 = Monday, etc.)
|
||||
* @returns {Object} - Filters object for the API call
|
||||
|
||||
export function buildActivityFilters(viewType, selectedDate, selectedYear, selectedActivityType, firstDayOfWeek = 0) {
|
||||
const filters = {
|
||||
type: selectedActivityType || null,
|
||||
};
|
||||
|
||||
if (viewType === "year") {
|
||||
// Note: Validation should be done before calling this function
|
||||
filters.start_date = `${selectedYear}-01-01`;
|
||||
filters.end_date = `${selectedYear + 1}-01-01`;
|
||||
} else if (viewType === "week" || viewType === "month") {
|
||||
const date = new Date(`${selectedDate}T00:00:00Z`);
|
||||
|
||||
if (viewType === "week") {
|
||||
const weekStart = getWeekStartDate(date, firstDayOfWeek);
|
||||
const weekEnd = getWeekEndDate(date, firstDayOfWeek);
|
||||
filters.start_date = formatDateISO(weekStart);
|
||||
filters.end_date = formatDateISO(weekEnd);
|
||||
} else {
|
||||
// month
|
||||
const monthStart = getMonthStartDate(date);
|
||||
const monthEnd = getMonthEndDate(date);
|
||||
filters.start_date = formatDateISO(monthStart);
|
||||
filters.end_date = formatDateISO(monthEnd);
|
||||
}
|
||||
}
|
||||
// For 'lifetime', no date filters are added
|
||||
|
||||
// Clean up null/empty values
|
||||
for (const key of Object.keys(filters)) {
|
||||
if (filters[key] == null || filters[key] === "") {
|
||||
delete filters[key];
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
} */
|
||||
/**
|
||||
* @param {string} selectedDate - The selected date in ISO format
|
||||
* @param {number} selectedYear - The selected year
|
||||
* @param {string} selectedActivityType - The selected activity type
|
||||
* @param {number} firstDayOfWeek - The first day of week preference (0 = Sunday, 1 = Monday, etc.)
|
||||
* @returns {Object} - Filters object for the API call
|
||||
*/
|
||||
export function buildActivityFilters(
|
||||
viewType,
|
||||
selectedDate,
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
/**
|
||||
* Validation Utilities
|
||||
* Validates an email address using RFC 5322 compliant regex.
|
||||
*
|
||||
* Common validation helpers for form inputs and data validation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validate email format using RFC 5322 standard
|
||||
*
|
||||
* @param email - Email address to validate
|
||||
* @returns True if email is valid, false otherwise
|
||||
* @param email - The email address to validate.
|
||||
* @returns `true` if the email is valid, `false` otherwise.
|
||||
*/
|
||||
export const isValidEmail = (email: string): boolean => {
|
||||
if (!email || typeof email !== 'string') {
|
||||
@@ -21,42 +15,42 @@ export const isValidEmail = (email: string): boolean => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a string is not empty or only whitespace
|
||||
* Checks if a string value is not empty after trimming whitespace.
|
||||
*
|
||||
* @param value - String to validate
|
||||
* @returns True if string has content, false otherwise
|
||||
* @param value - The string to check.
|
||||
* @returns `true` if the value is non-empty, `false` otherwise.
|
||||
*/
|
||||
export const isNotEmpty = (value: string): boolean => {
|
||||
return typeof value === 'string' && value.trim().length > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate minimum length for a string
|
||||
* Checks if a string meets a minimum length requirement.
|
||||
*
|
||||
* @param value - String to validate
|
||||
* @param minLength - Minimum required length
|
||||
* @returns True if string meets minimum length, false otherwise
|
||||
* @param value - The string to check.
|
||||
* @param minLength - The minimum required length.
|
||||
* @returns `true` if the value meets the minimum length, `false` otherwise.
|
||||
*/
|
||||
export const hasMinLength = (value: string, minLength: number): boolean => {
|
||||
return typeof value === 'string' && value.length >= minLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate maximum length for a string
|
||||
* Checks if a string does not exceed a maximum length.
|
||||
*
|
||||
* @param value - String to validate
|
||||
* @param maxLength - Maximum allowed length
|
||||
* @returns True if string is within max length, false otherwise
|
||||
* @param value - The string to check.
|
||||
* @param maxLength - The maximum allowed length.
|
||||
* @returns `true` if the value is within the maximum length, `false` otherwise.
|
||||
*/
|
||||
export const hasMaxLength = (value: string, maxLength: number): boolean => {
|
||||
return typeof value === 'string' && value.length <= maxLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate MFA code format (typically 6 digits)
|
||||
* Validates a multi-factor authentication code.
|
||||
*
|
||||
* @param code - MFA code to validate
|
||||
* @returns True if code is valid format, false otherwise
|
||||
* @param code - The MFA code to validate.
|
||||
* @returns `true` if the code is valid (4-8 digits), `false` otherwise.
|
||||
*/
|
||||
export const isValidMFACode = (code: string): boolean => {
|
||||
if (!code || typeof code !== 'string') {
|
||||
@@ -68,10 +62,10 @@ export const isValidMFACode = (code: string): boolean => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize input string by trimming whitespace and removing potentially harmful characters
|
||||
* Sanitizes user input by trimming whitespace.
|
||||
*
|
||||
* @param input - String to sanitize
|
||||
* @returns Sanitized string
|
||||
* @param input - The input string to sanitize.
|
||||
* @returns The trimmed string, or empty string if input is not a string.
|
||||
*/
|
||||
export const sanitizeInput = (input: string): string => {
|
||||
if (typeof input !== 'string') {
|
||||
@@ -80,67 +74,55 @@ export const sanitizeInput = (input: string): string => {
|
||||
return input.trim()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Password Validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Password validation requirements interface
|
||||
* @interface PasswordRequirements
|
||||
* Defines password validation requirements.
|
||||
*
|
||||
* @property minLength - Minimum password length.
|
||||
* @property requireUppercase - Whether uppercase letters are required.
|
||||
* @property requireLowercase - Whether lowercase letters are required.
|
||||
* @property requireDigit - Whether digits are required.
|
||||
* @property requireSpecialChar - Whether special characters are required.
|
||||
*/
|
||||
export interface PasswordRequirements {
|
||||
/** Minimum password length */
|
||||
minLength: number
|
||||
/** Requires at least one uppercase letter */
|
||||
requireUppercase: boolean
|
||||
/** Requires at least one lowercase letter */
|
||||
requireLowercase: boolean
|
||||
/** Requires at least one digit */
|
||||
requireDigit: boolean
|
||||
/** Requires at least one special character */
|
||||
requireSpecialChar: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Password strength analysis result
|
||||
* @interface PasswordStrength
|
||||
* Describes password strength assessment results.
|
||||
*
|
||||
* @property isValid - Whether the password meets all requirements.
|
||||
* @property score - Numeric strength score.
|
||||
* @property failures - List of failed validation criteria.
|
||||
* @property strengthLevel - Overall strength level classification.
|
||||
*/
|
||||
export interface PasswordStrength {
|
||||
/** Whether password meets all requirements */
|
||||
isValid: boolean
|
||||
/** Overall strength score (0-100) */
|
||||
score: number
|
||||
/** Specific requirement failures */
|
||||
failures: string[]
|
||||
/** Descriptive strength level */
|
||||
strengthLevel: 'weak' | 'fair' | 'good' | 'strong' | 'very-strong'
|
||||
}
|
||||
|
||||
/**
|
||||
* Default password requirements matching backend validation
|
||||
* Requirements: min 8 chars, 1 uppercase, 1 digit, 1 special character
|
||||
* Default password validation requirements for the application.
|
||||
*/
|
||||
export const DEFAULT_PASSWORD_REQUIREMENTS: PasswordRequirements = {
|
||||
minLength: 8,
|
||||
requireUppercase: true,
|
||||
requireLowercase: false, // Not explicitly required but usually present
|
||||
requireLowercase: true,
|
||||
requireDigit: true,
|
||||
requireSpecialChar: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate password against standard requirements
|
||||
* Default requirements: min 8 chars, 1 uppercase, 1 digit, 1 special character
|
||||
* Validates a password against specified requirements.
|
||||
*
|
||||
* @param password - Password to validate
|
||||
* @param requirements - Optional custom requirements (defaults to standard)
|
||||
* @returns True if password meets requirements, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* isValidPassword('MyPass123!') // true
|
||||
* isValidPassword('weakpass') // false - no uppercase, digit, or special char
|
||||
* ```
|
||||
* @param password - The password to validate.
|
||||
* @param requirements - The password requirements to check against.
|
||||
* @returns `true` if the password meets all requirements, `false` otherwise.
|
||||
*/
|
||||
export const isValidPassword = (
|
||||
password: string,
|
||||
@@ -180,17 +162,11 @@ export const isValidPassword = (
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that two passwords match (for confirmation fields)
|
||||
* Checks if two password strings match exactly.
|
||||
*
|
||||
* @param password - Original password
|
||||
* @param confirmPassword - Confirmation password
|
||||
* @returns True if passwords match, false otherwise
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* passwordsMatch('MyPass123!', 'MyPass123!') // true
|
||||
* passwordsMatch('MyPass123!', 'DifferentPass') // false
|
||||
* ```
|
||||
* @param password - The original password.
|
||||
* @param confirmPassword - The confirmation password.
|
||||
* @returns `true` if both passwords match, `false` otherwise.
|
||||
*/
|
||||
export const passwordsMatch = (password: string, confirmPassword: string): boolean => {
|
||||
return (
|
||||
|
||||
@@ -151,52 +151,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* LoginView Component
|
||||
*
|
||||
* Handles user authentication with support for:
|
||||
* - Standard username/password login
|
||||
* - Multi-Factor Authentication (MFA)
|
||||
* - Password reset functionality
|
||||
* - Route-based notification handling
|
||||
*
|
||||
* @component
|
||||
*/
|
||||
|
||||
// Vue composition API
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
// Router
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
// Internationalization
|
||||
import { useI18n } from 'vue-i18n'
|
||||
// Notifications
|
||||
import { push } from 'notivue'
|
||||
// Stores
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
import { useServerSettingsStore } from '@/stores/serverSettingsStore'
|
||||
// Services
|
||||
import { session } from '@/services/sessionService'
|
||||
import { passwordReset } from '@/services/passwordResetService'
|
||||
import { profile } from '@/services/profileService'
|
||||
import { identityProviders } from '@/services/identityProvidersService'
|
||||
// Components
|
||||
import ModalComponentEmailInput from '@/components/Modals/ModalComponentEmailInput.vue'
|
||||
import LoadingComponent from '@/components/GeneralComponents/LoadingComponent.vue'
|
||||
// Composables
|
||||
import { useBootstrapModal } from '@/composables/useBootstrapModal'
|
||||
// Types
|
||||
import type { RouteQueryHandlers, LoginResponse, ErrorWithResponse, SSOProvider } from '@/types'
|
||||
// Constants
|
||||
import { HTTP_STATUS, QUERY_PARAM_TRUE, extractStatusCode } from '@/constants/httpConstants'
|
||||
import { PROVIDER_CUSTOM_LOGO_MAP } from '@/constants/ssoConstants'
|
||||
// Utils
|
||||
import { isNotEmpty, sanitizeInput } from '@/utils/validationUtils'
|
||||
// Assets
|
||||
import defaultLoginImage from '@/assets/login.png'
|
||||
|
||||
/**
|
||||
* Route query parameter handlers configuration
|
||||
* Maps URL query parameters to notification types and i18n keys
|
||||
* Maps URL query parameters to notification types and i18n translation keys.
|
||||
*/
|
||||
const ROUTE_QUERY_HANDLERS: RouteQueryHandlers = {
|
||||
sessionExpired: { type: 'warning', key: 'loginView.sessionExpired' },
|
||||
@@ -210,20 +185,12 @@ const ROUTE_QUERY_HANDLERS: RouteQueryHandlers = {
|
||||
verifyEmailInvalidLink: { type: 'error', key: 'loginView.verifyEmailInvalidLink' }
|
||||
} as const
|
||||
|
||||
// ============================================================================
|
||||
// Composables & Store Initialization
|
||||
// ============================================================================
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { locale, t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
const serverSettingsStore = useServerSettingsStore()
|
||||
|
||||
// ============================================================================
|
||||
// Modal Management
|
||||
// ============================================================================
|
||||
|
||||
const forgotPasswordModalRef = ref<typeof ModalComponentEmailInput | null>(null)
|
||||
const {
|
||||
initializeModal,
|
||||
@@ -233,10 +200,6 @@ const {
|
||||
} = useBootstrapModal()
|
||||
const forgotPasswordLoading = ref(false)
|
||||
|
||||
// ============================================================================
|
||||
// Form State
|
||||
// ============================================================================
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const mfaCode = ref('')
|
||||
@@ -245,26 +208,20 @@ const loading = ref(false)
|
||||
const pendingUsername = ref('')
|
||||
const showPassword = ref(false)
|
||||
|
||||
// ============================================================================
|
||||
// SSO State
|
||||
// ============================================================================
|
||||
|
||||
const ssoProviders = ref<SSOProvider[]>([])
|
||||
const loadingSSOProviders = ref(true)
|
||||
|
||||
// ============================================================================
|
||||
// Computed Properties
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Shorthand for server settings
|
||||
* Reduces verbosity and improves reactivity tracking
|
||||
* Computed reference to server settings from the store.
|
||||
*
|
||||
* @returns The current server settings object.
|
||||
*/
|
||||
const serverSettings = computed(() => serverSettingsStore.serverSettings)
|
||||
|
||||
/**
|
||||
* Compute the login photo URL from server settings
|
||||
* Returns custom photo from server if set, otherwise default image
|
||||
* Computes the login photo URL based on server settings.
|
||||
*
|
||||
* @returns The URL of the custom login photo or default image.
|
||||
*/
|
||||
const loginPhotoUrl = computed<string>(() =>
|
||||
serverSettings.value.login_photo_set
|
||||
@@ -272,31 +229,28 @@ const loginPhotoUrl = computed<string>(() =>
|
||||
: defaultLoginImage
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// UI Interaction Handlers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Show the forgot password modal
|
||||
* Shows the forgot password modal.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const showForgotPasswordModal = (): void => {
|
||||
showForgotModal()
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle password field visibility
|
||||
* Toggles the visibility of the password field.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const togglePasswordVisibility = (): void => {
|
||||
showPassword.value = !showPassword.value
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Authentication Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Main form submission handler
|
||||
* Routes to either MFA verification or standard login based on state
|
||||
* Handles form submission by routing to either standard login or MFA verification.
|
||||
*
|
||||
* @returns A promise that resolves when submission is complete.
|
||||
*/
|
||||
const submitForm = async (): Promise<void> => {
|
||||
if (mfaRequired.value) {
|
||||
@@ -307,8 +261,10 @@ const submitForm = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle standard username/password login
|
||||
* Initiates authentication and checks for MFA requirement
|
||||
* Handles standard username/password login authentication.
|
||||
*
|
||||
* @returns A promise that resolves when login attempt is complete.
|
||||
* @throws {ErrorWithResponse} When authentication fails.
|
||||
*/
|
||||
const submitLogin = async (): Promise<void> => {
|
||||
// Create the form data
|
||||
@@ -340,8 +296,10 @@ const submitLogin = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Multi-Factor Authentication verification
|
||||
* Validates MFA code and completes login if successful
|
||||
* Handles multi-factor authentication verification.
|
||||
*
|
||||
* @returns A promise that resolves when MFA verification is complete.
|
||||
* @throws {ErrorWithResponse} When MFA verification fails.
|
||||
*/
|
||||
const submitMFAVerification = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -366,10 +324,11 @@ const submitMFAVerification = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the login process after successful authentication
|
||||
* Fetches user profile, updates auth store, and redirects to home
|
||||
* Completes the login process after successful authentication.
|
||||
*
|
||||
* @param session_id - Session identifier from authentication response
|
||||
* @param session_id - The session identifier from authentication response.
|
||||
* @returns A promise that resolves when login completion and redirect are done.
|
||||
* @throws {Error} When profile fetch or navigation fails.
|
||||
*/
|
||||
const completeLogin = async (session_id: string): Promise<void> => {
|
||||
// Get logged user information
|
||||
@@ -382,15 +341,11 @@ const completeLogin = async (session_id: string): Promise<void> => {
|
||||
await router.push('/')
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Handling
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle login errors with appropriate user feedback
|
||||
* Maps HTTP status codes to localized error messages
|
||||
* Handles login errors and displays appropriate user-friendly messages.
|
||||
*
|
||||
* @param error - Error object from authentication attempt
|
||||
* @param error - The error object from authentication attempt.
|
||||
* @returns void
|
||||
*/
|
||||
const handleLoginError = (error: ErrorWithResponse): void => {
|
||||
const statusCode = extractStatusCode(error)
|
||||
@@ -410,15 +365,12 @@ const handleLoginError = (error: ErrorWithResponse): void => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Password Reset Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handle forgot password form submission
|
||||
* Validates email and sends password reset request
|
||||
* Handles forgot password form submission.
|
||||
*
|
||||
* @param email - User's email address for password reset
|
||||
* @param email - The user's email address for password reset.
|
||||
* @returns A promise that resolves when the reset request is complete.
|
||||
* @throws {Error} When the password reset request fails.
|
||||
*/
|
||||
const handleForgotPasswordSubmit = async (email: string): Promise<void> => {
|
||||
// Validate email input
|
||||
@@ -449,16 +401,12 @@ const handleForgotPasswordSubmit = async (email: string): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SSO Logic
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fetch enabled SSO providers from the API
|
||||
* Loads public list of identity providers for display on login page
|
||||
* Fetches enabled SSO providers from the API.
|
||||
*
|
||||
* @returns A promise that resolves when providers are fetched.
|
||||
*/
|
||||
const fetchSSOProviders = async (): Promise<void> => {
|
||||
// Only fetch if SSO is enabled
|
||||
if (!serverSettings.value.sso_enabled) {
|
||||
loadingSSOProviders.value = false
|
||||
return
|
||||
@@ -467,7 +415,6 @@ const fetchSSOProviders = async (): Promise<void> => {
|
||||
try {
|
||||
ssoProviders.value = await identityProviders.getEnabledProviders()
|
||||
} catch (error) {
|
||||
// Silent fail - login page should still work without SSO
|
||||
ssoProviders.value = []
|
||||
} finally {
|
||||
loadingSSOProviders.value = false
|
||||
@@ -475,11 +422,10 @@ const fetchSSOProviders = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a provider has a custom logo
|
||||
* Returns the logo path if available
|
||||
* Gets the custom logo path for an SSO provider.
|
||||
*
|
||||
* @param iconName - Provider icon name
|
||||
* @returns Custom logo path or null
|
||||
* @param iconName - The provider icon name.
|
||||
* @returns The custom logo path or `null` if not available.
|
||||
*/
|
||||
const getProviderCustomLogo = (iconName?: string): string | null => {
|
||||
if (!iconName) return null
|
||||
@@ -489,26 +435,21 @@ const getProviderCustomLogo = (iconName?: string): string | null => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SSO login button click
|
||||
* Redirects to SSO provider authorization page
|
||||
* Initiates SSO login for the specified provider.
|
||||
*
|
||||
* @param slug - Provider slug identifier
|
||||
* @param slug - The provider slug identifier.
|
||||
* @returns void
|
||||
*/
|
||||
const handleSSOLogin = (slug: string): void => {
|
||||
identityProviders.initiateLogin(slug)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for SSO auto-redirect
|
||||
* Automatically redirects to SSO provider if:
|
||||
* - SSO is enabled
|
||||
* - Auto-redirect is enabled
|
||||
* - Exactly one provider is configured
|
||||
* - Local login is disabled
|
||||
* - No query parameters present (to avoid redirect loops)
|
||||
* Checks if SSO auto-redirect should occur and redirects if conditions are met.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const checkSSOAutoRedirect = (): void => {
|
||||
// Check all conditions for auto-redirect
|
||||
if (
|
||||
serverSettings.value.sso_enabled &&
|
||||
serverSettings.value.sso_auto_redirect &&
|
||||
@@ -516,7 +457,6 @@ const checkSSOAutoRedirect = (): void => {
|
||||
ssoProviders.value.length === 1 &&
|
||||
Object.keys(route.query).length === 0
|
||||
) {
|
||||
// Auto-redirect to the single SSO provider
|
||||
const provider = ssoProviders.value[0]
|
||||
if (provider) {
|
||||
handleSSOLogin(provider.slug)
|
||||
@@ -525,11 +465,11 @@ const checkSSOAutoRedirect = (): void => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process SSO callback query parameters
|
||||
* Handles success and error states from SSO authentication
|
||||
* Processes SSO callback query parameters and handles success or error states.
|
||||
*
|
||||
* @returns A promise that resolves when callback processing is complete.
|
||||
*/
|
||||
const processSSOCallback = async (): Promise<void> => {
|
||||
// Check for SSO success
|
||||
if (route.query.sso === 'success' && route.query.session_id) {
|
||||
push.success(t('loginView.ssoSuccess'))
|
||||
|
||||
@@ -541,7 +481,6 @@ const processSSOCallback = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for SSO error
|
||||
if (route.query.error) {
|
||||
const errorType = route.query.error as string
|
||||
switch (errorType) {
|
||||
@@ -566,13 +505,10 @@ const processSSOCallback = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Route & Notification Handling
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Process route query parameters and display appropriate notifications
|
||||
* Checks for specific query parameters and shows corresponding messages
|
||||
* Processes route query parameters and displays appropriate notifications.
|
||||
*
|
||||
* @returns A promise that resolves when all query parameters are processed.
|
||||
*/
|
||||
const processRouteQueryParameters = async (): Promise<void> => {
|
||||
Object.entries(ROUTE_QUERY_HANDLERS).forEach(([param, config]) => {
|
||||
@@ -581,35 +517,27 @@ const processRouteQueryParameters = async (): Promise<void> => {
|
||||
}
|
||||
})
|
||||
|
||||
// Process SSO-specific callbacks
|
||||
await processSSOCallback()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle Hooks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Component mounted lifecycle hook
|
||||
* Initializes modal, fetches SSO providers, and processes route parameters
|
||||
* Lifecycle hook that runs when the component is mounted.
|
||||
* Initializes modal, fetches SSO providers, processes route parameters, and checks for auto-redirect.
|
||||
*
|
||||
* @returns A promise that resolves when initialization is complete.
|
||||
*/
|
||||
onMounted(async () => {
|
||||
// Initialize forgot password modal
|
||||
await initializeModal(forgotPasswordModalRef)
|
||||
|
||||
// Fetch SSO providers if enabled
|
||||
await fetchSSOProviders()
|
||||
|
||||
// Process any route query parameters for notifications
|
||||
await processRouteQueryParameters()
|
||||
|
||||
// Check for SSO auto-redirect (must be after providers are fetched)
|
||||
checkSSOAutoRedirect()
|
||||
})
|
||||
|
||||
/**
|
||||
* Component unmounted lifecycle hook
|
||||
* Cleanup modal resources
|
||||
* Lifecycle hook that runs when the component is unmounted.
|
||||
* Cleans up modal resources.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
onUnmounted(() => {
|
||||
disposeModal()
|
||||
|
||||
@@ -294,20 +294,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @fileoverview SignUpView Component
|
||||
*
|
||||
* User registration view with comprehensive form validation and optional profile fields.
|
||||
* User registration view with form validation and optional profile fields.
|
||||
* Handles user signup with required and optional information including personal details,
|
||||
* preferences, and physical attributes. Supports both metric and imperial unit systems.
|
||||
*
|
||||
* @component
|
||||
* @example
|
||||
* <SignUpView />
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Imports
|
||||
// ============================================================================
|
||||
import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
|
||||
import { useRouter, type Router } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -320,186 +311,170 @@ import { HTTP_STATUS, QUERY_PARAM_TRUE, extractStatusCode } from '@/constants/ht
|
||||
import type { ErrorWithResponse } from '@/types'
|
||||
import defaultLoginImage from '@/assets/login.png'
|
||||
|
||||
// ============================================================================
|
||||
// Interfaces
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* User signup request data structure
|
||||
* @interface SignUpRequestData
|
||||
* User signup request data structure.
|
||||
*
|
||||
* @property name - User's full name.
|
||||
* @property username - Unique username (lowercase).
|
||||
* @property email - User's email address (lowercase).
|
||||
* @property password - User's password.
|
||||
* @property preferred_language - Preferred language code (e.g., 'us', 'pt', 'es').
|
||||
* @property city - User's city of residence.
|
||||
* @property birthdate - User's birth date in ISO format.
|
||||
* @property gender - Gender identifier (1=male, 2=female, 3=unspecified).
|
||||
* @property units - Unit system preference (1=metric, 2=imperial).
|
||||
* @property height - User's height in centimeters.
|
||||
* @property first_day_of_week - First day of week (0=Sunday, 1=Monday, etc.).
|
||||
* @property currency - Currency preference (1=Euro, 2=Dollar, 3=Pound).
|
||||
*/
|
||||
interface SignUpRequestData {
|
||||
/** User's full name */
|
||||
name: string
|
||||
/** Unique username (lowercase) */
|
||||
username: string
|
||||
/** User's email address (lowercase) */
|
||||
email: string
|
||||
/** User's password */
|
||||
password: string
|
||||
/** Preferred language code (e.g., 'us', 'pt', 'es') */
|
||||
preferred_language: string
|
||||
/** User's city of residence */
|
||||
city: string | null
|
||||
/** User's birth date in ISO format */
|
||||
birthdate: string | null
|
||||
/** Gender identifier (1=male, 2=female, 3=unspecified) */
|
||||
gender: number
|
||||
/** Unit system preference (1=metric, 2=imperial) */
|
||||
units: number
|
||||
/** User's height in centimeters */
|
||||
height: number | null
|
||||
/** First day of week (0=Sunday, 1=Monday, etc.) */
|
||||
first_day_of_week: number
|
||||
/** Currency preference (1=Euro, 2=Dollar, 3=Pound) */
|
||||
currency: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Signup API response structure
|
||||
* @interface SignUpResponse
|
||||
* Signup API response structure.
|
||||
*
|
||||
* @property email_verification_required - Whether email verification is required.
|
||||
* @property admin_approval_required - Whether admin approval is required.
|
||||
*/
|
||||
interface SignUpResponse {
|
||||
/** Whether email verification is required */
|
||||
email_verification_required: boolean
|
||||
/** Whether admin approval is required */
|
||||
admin_approval_required: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Login route query parameters for post-signup redirect
|
||||
* @interface LoginQueryParams
|
||||
* Login route query parameters for post-signup redirect.
|
||||
*
|
||||
* @property emailVerificationSent - Email verification status flag.
|
||||
* @property adminApprovalRequired - Admin approval status flag.
|
||||
*/
|
||||
interface LoginQueryParams {
|
||||
/** Email verification status flag */
|
||||
emailVerificationSent?: string
|
||||
/** Admin approval status flag */
|
||||
adminApprovalRequired?: string
|
||||
/** Index signature for Vue Router compatibility */
|
||||
[key: string]: string | undefined
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Composables & Stores
|
||||
// ============================================================================
|
||||
const router: Router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const serverSettingsStore = useServerSettingsStore()
|
||||
|
||||
// ============================================================================
|
||||
// Reactive State - Form Data
|
||||
// ============================================================================
|
||||
/** Loading state during form submission. */
|
||||
const isLoading = ref(false)
|
||||
|
||||
/** Loading state during form submission */
|
||||
const isLoading: Ref<boolean> = ref(false)
|
||||
/** User's full name. */
|
||||
const signUpName = ref('')
|
||||
|
||||
/** User's full name */
|
||||
const signUpName: Ref<string> = ref('')
|
||||
/** User's username. */
|
||||
const signUpUsername = ref('')
|
||||
|
||||
/** User's username */
|
||||
const signUpUsername: Ref<string> = ref('')
|
||||
/** User's email address. */
|
||||
const signUpEmail = ref('')
|
||||
|
||||
/** User's email address */
|
||||
const signUpEmail: Ref<string> = ref('')
|
||||
/** User's password. */
|
||||
const signUpPassword = ref('')
|
||||
|
||||
/** User's password */
|
||||
const signUpPassword: Ref<string> = ref('')
|
||||
/** User's preferred language. */
|
||||
const signUpPreferredLanguage = ref('us')
|
||||
|
||||
/** User's preferred language */
|
||||
const signUpPreferredLanguage: Ref<string> = ref('us')
|
||||
/** User's city. */
|
||||
const signUpCity = ref('')
|
||||
|
||||
/** User's city */
|
||||
const signUpCity: Ref<string> = ref('')
|
||||
/** User's birth date. */
|
||||
const signUpBirthdate = ref('')
|
||||
|
||||
/** User's birth date */
|
||||
const signUpBirthdate: Ref<string> = ref('')
|
||||
/** User's gender (1=male, 2=female, 3=unspecified). */
|
||||
const signUpGender = ref(1)
|
||||
|
||||
/** User's gender (1=male, 2=female, 3=unspecified) */
|
||||
const signUpGender: Ref<number> = ref(1)
|
||||
/** User's unit preference (1=metric, 2=imperial). */
|
||||
const signUpUnits = ref(Number(serverSettingsStore.serverSettings.units))
|
||||
|
||||
/** User's unit preference (1=metric, 2=imperial) */
|
||||
const signUpUnits: Ref<number> = ref(Number(serverSettingsStore.serverSettings.units))
|
||||
/** User's height in centimeters. */
|
||||
const signUpHeightCms = ref<number | null>(null)
|
||||
|
||||
/** User's height in centimeters */
|
||||
const signUpHeightCms: Ref<number | null> = ref(null)
|
||||
/** User's height in feet (imperial). */
|
||||
const signUpHeightFeet = ref<number | null>(null)
|
||||
|
||||
/** User's height in feet (imperial) */
|
||||
const signUpHeightFeet: Ref<number | null> = ref(null)
|
||||
/** User's height in inches (imperial). */
|
||||
const signUpHeightInches = ref<number | null>(null)
|
||||
|
||||
/** User's height in inches (imperial) */
|
||||
const signUpHeightInches: Ref<number | null> = ref(null)
|
||||
/** First day of week preference (0=Sunday, 1=Monday, etc.). */
|
||||
const signUpFirstDayOfWeek = ref(1)
|
||||
|
||||
/** First day of week preference (0=Sunday, 1=Monday, etc.) */
|
||||
const signUpFirstDayOfWeek: Ref<number> = ref(1)
|
||||
/** Currency preference (1=Euro, 2=Dollar, 3=Pound). */
|
||||
const signUpCurrency = ref(Number(serverSettingsStore.serverSettings.currency))
|
||||
|
||||
/** Currency preference (1=Euro, 2=Dollar, 3=Pound) */
|
||||
const signUpCurrency: Ref<number> = ref(Number(serverSettingsStore.serverSettings.currency))
|
||||
/** Password visibility toggle state. */
|
||||
const showPassword = ref(false)
|
||||
|
||||
// ============================================================================
|
||||
// Reactive State - UI State
|
||||
// ============================================================================
|
||||
|
||||
/** Password visibility toggle state */
|
||||
const showPassword: Ref<boolean> = ref(false)
|
||||
|
||||
/** Optional fields section visibility state */
|
||||
const showOptionalFields: Ref<boolean> = ref(false)
|
||||
|
||||
// ============================================================================
|
||||
// Computed Properties - Validation
|
||||
// ============================================================================
|
||||
/** Optional fields section visibility state. */
|
||||
const showOptionalFields = ref(false)
|
||||
|
||||
/**
|
||||
* Validates feet input for imperial height
|
||||
* Range: 0-10 feet
|
||||
* Validates feet input for imperial height.
|
||||
*
|
||||
* @returns `true` if feet value is between 0 and 10, or `null`.
|
||||
*/
|
||||
const isFeetValid: ComputedRef<boolean> = computed(() => {
|
||||
const isFeetValid = computed(() => {
|
||||
if (signUpHeightFeet.value === null) return true
|
||||
return signUpHeightFeet.value >= 0 && signUpHeightFeet.value <= 10
|
||||
})
|
||||
|
||||
/**
|
||||
* Validates inches input for imperial height
|
||||
* Range: 0-11 inches
|
||||
* Validates inches input for imperial height.
|
||||
*
|
||||
* @returns `true` if inches value is between 0 and 11, or `null`.
|
||||
*/
|
||||
const isInchesValid: ComputedRef<boolean> = computed(() => {
|
||||
const isInchesValid = computed(() => {
|
||||
if (signUpHeightInches.value === null) return true
|
||||
return signUpHeightInches.value >= 0 && signUpHeightInches.value <= 11
|
||||
})
|
||||
|
||||
/**
|
||||
* Validates email format using RFC 5322 compliant regex
|
||||
* Validates email format using RFC 5322 compliant regex.
|
||||
*
|
||||
* @returns `true` if email is valid or empty.
|
||||
*/
|
||||
const isEmailValid: ComputedRef<boolean> = computed(() => {
|
||||
const isEmailValid = computed(() => {
|
||||
if (!signUpEmail.value) return true
|
||||
return isValidEmail(signUpEmail.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Validates password strength using centralized validation
|
||||
* Requirements: min 8 chars, 1 uppercase, 1 digit, 1 special character
|
||||
* Validates password strength using centralized validation.
|
||||
*
|
||||
* @returns `true` if password meets requirements (min 8 chars, 1 uppercase, 1 digit, 1 special character) or is empty.
|
||||
*/
|
||||
const isPasswordValid: ComputedRef<boolean> = computed(() => {
|
||||
const isPasswordValid = computed(() => {
|
||||
if (!signUpPassword.value) return true
|
||||
return isValidPassword(signUpPassword.value)
|
||||
})
|
||||
|
||||
/**
|
||||
* Compute the login photo URL from server settings
|
||||
* Returns custom photo from server if set, otherwise default image
|
||||
* Computes the login photo URL from server settings.
|
||||
*
|
||||
* @returns Custom photo URL from server if set, otherwise default image.
|
||||
*/
|
||||
const loginPhotoUrl: ComputedRef<string> = computed(() => {
|
||||
const loginPhotoUrl = computed(() => {
|
||||
return serverSettingsStore.serverSettings.login_photo_set
|
||||
? `${window.env.ENDURAIN_HOST}/server_images/login.png`
|
||||
: defaultLoginImage
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Methods - UI Interactions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Toggles password visibility between plain text and masked
|
||||
* Toggles password visibility between plain text and masked.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const togglePasswordVisibility = (): void => {
|
||||
showPassword.value = !showPassword.value
|
||||
@@ -510,11 +485,11 @@ const togglePasswordVisibility = (): void => {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Handles form submission for user signup
|
||||
* Converts height units, sanitizes inputs, submits data, and redirects on success
|
||||
* Handles form submission for user signup.
|
||||
* Converts height units, sanitizes inputs, submits data, and redirects on success.
|
||||
*
|
||||
* @async
|
||||
* @throws {Error} When signup fails or validation errors occur
|
||||
* @returns A promise that resolves when signup is complete.
|
||||
* @throws {ErrorWithResponse} When signup fails or validation errors occur.
|
||||
*/
|
||||
const submitForm = async (): Promise<void> => {
|
||||
// Convert height units based on server settings
|
||||
@@ -574,9 +549,10 @@ const submitForm = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles signup errors and displays appropriate error messages
|
||||
* Handles signup errors and displays appropriate error messages.
|
||||
*
|
||||
* @param {ErrorWithResponse} error - Error object with response data
|
||||
* @param error - Error object with response data.
|
||||
* @returns void
|
||||
*/
|
||||
const handleSignUpError = (error: ErrorWithResponse): void => {
|
||||
const statusCode = extractStatusCode(error)
|
||||
@@ -601,8 +577,10 @@ const handleSignUpError = (error: ErrorWithResponse): void => {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Component mounted lifecycle hook
|
||||
* Checks if signup is enabled and redirects to login if disabled
|
||||
* Component mounted lifecycle hook.
|
||||
* Checks if signup is enabled and redirects to login if disabled.
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
onMounted(() => {
|
||||
if (!serverSettingsStore.serverSettings.signup_enabled) {
|
||||
|
||||
Reference in New Issue
Block a user