diff --git a/README.md b/README.md
index 139c0f31..1c5e3234 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,670 @@
-# IP-RMT60
\ No newline at end of file
+# Dokumentasi API
+
+## Endpoint Xiaomi Device Store
+
+Berikut adalah daftar endpoint yang tersedia:
+
+- `GET /public/devices`
+- `GET /public/devices/:id`
+- `POST /ai`
+
+- `POST /register`
+- `POST /login`
+- `POST /login/google`
+
+- `GET /devices`
+- `GET /devices/:id`
+- `PUT /users/update`
+- `POST /favorites/:XiaomiDeviceId`
+- `GET /favorites`
+- `DELETE /favorites/:XiaomiDeviceId`
+
+
+
+## 1. GET /public/devices
+
+Description:
+
+- Mendapatkan daftar semua perangkat Xiaomi dengan informasi terbatas
+
+Request:
+
+- Query parameters:
+
+```json
+{
+ "search": "string (optional)",
+ "sort": "string (optional, format: field:order, contoh: price:asc)",
+ "minPrice": "integer (optional)",
+ "maxPrice": "integer (optional)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+[
+ {
+ "id": 1,
+ "key": "xiaomi-13-pro",
+ "device_name": "Xiaomi 13 Pro",
+ "device_image": "url_gambar",
+ "price": 12000000
+ }
+]
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 2. GET /public/devices/:id
+
+Description:
+
+- Mendapatkan detail perangkat Xiaomi berdasarkan ID dengan informasi terbatas
+
+Request:
+
+- params:
+
+```json
+{
+ "id": "integer (required)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "id": 1,
+ "key": "xiaomi-13-pro",
+ "device_name": "Xiaomi 13 Pro",
+ "device_image": "url_gambar",
+ "price": 12000000
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "Device tidak ditemukan"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 3. POST /ai
+
+Description:
+
+- Mendapatkan informasi menggunakan AI Gemini tentang perangkat Xiaomi
+
+Request:
+
+- body:
+
+```json
+{
+ "prompt": "string (required)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "response": "string"
+}
+```
+
+_Response (400 - Bad Request)_
+
+```json
+{
+ "message": "Prompt diperlukan"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 4. POST /register
+
+Description:
+
+- Registrasi user baru
+
+Request:
+
+- body:
+
+```json
+{
+ "username": "string",
+ "email": "string",
+ "password": "string"
+}
+```
+
+_Response (201 - Created)_
+
+```json
+{
+ "id": "integer",
+ "username": "string",
+ "email": "string"
+}
+```
+
+_Response (400 - Bad Request)_
+
+```json
+{
+ "message": "Username tidak boleh kosong"
+}
+OR
+{
+ "message": "Email tidak boleh kosong"
+}
+OR
+{
+ "message": "Password tidak boleh kosong"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 5. POST /login
+
+Description:
+
+- Login user
+
+Request:
+
+- body:
+
+```json
+{
+ "email": "string",
+ "password": "string"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "access_token": "string",
+ "id": "integer",
+ "username": "string",
+ "email": "string"
+}
+```
+
+_Response (400 - Bad Request)_
+
+```json
+{
+ "message": "Email and password are required"
+}
+```
+
+_Response (401 - Unauthorized)_
+
+```json
+{
+ "message": "Invalid password"
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "User not found"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 6. POST /login/google
+
+Description:
+
+- Login dengan Google
+
+Request:
+
+- body:
+
+```json
+{
+ "googleToken": "string"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "access_token": "string",
+ "id": "integer",
+ "username": "string",
+ "email": "string"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 7. GET /devices
+
+Description:
+
+- Mendapatkan daftar semua perangkat Xiaomi dengan informasi lengkap
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+[
+ {
+ "id": 1,
+ "key": "xiaomi-13-pro",
+ "device_name": "Xiaomi 13 Pro",
+ "device_image": "url_gambar",
+ "display_size": "6.73 inci",
+ "display_res": "1440 x 3200 pixel",
+ "camera": "50MP + 50MP + 50MP",
+ "video": "8K@24fps",
+ "ram": "12GB",
+ "chipset": "Snapdragon 8 Gen 2",
+ "battery": "4820 mAh",
+ "batteryType": "Li-Po",
+ "body": "Ceramic/Glass",
+ "os_type": "Android 13, MIUI 14",
+ "storage": "256GB/512GB",
+ "comment": "Flagship 2023",
+ "price": 12000000
+ }
+]
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 8. GET /devices/:id
+
+Description:
+
+- Mendapatkan detail perangkat Xiaomi berdasarkan ID dengan informasi lengkap
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+- params:
+
+```json
+{
+ "id": "integer (required)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "id": 1,
+ "key": "xiaomi-13-pro",
+ "device_name": "Xiaomi 13 Pro",
+ "device_image": "url_gambar",
+ "display_size": "6.73 inci",
+ "display_res": "1440 x 3200 pixel",
+ "camera": "50MP + 50MP + 50MP",
+ "video": "8K@24fps",
+ "ram": "12GB",
+ "chipset": "Snapdragon 8 Gen 2",
+ "battery": "4820 mAh",
+ "batteryType": "Li-Po",
+ "body": "Ceramic/Glass",
+ "os_type": "Android 13, MIUI 14",
+ "storage": "256GB/512GB",
+ "comment": "Flagship 2023",
+ "price": 12000000
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "Device tidak ditemukan"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 9. PUT /users/update
+
+Description:
+
+- Memperbarui data profil user
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+- body:
+
+```json
+{
+ "username": "string (optional)",
+ "email": "string (optional)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "message": "User berhasil diupdate",
+ "user": {
+ "id": "integer",
+ "username": "string",
+ "email": "string"
+ }
+}
+```
+
+_Response (400 - Bad Request)_
+
+```json
+{
+ "message": "Tidak ada data yang diupdate"
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "User tidak ditemukan"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 10. POST /favorites/:XiaomiDeviceId
+
+Description:
+
+- Menambahkan device ke daftar favorit
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+- params:
+
+```json
+{
+ "XiaomiDeviceId": "integer (required)"
+}
+```
+
+_Response (201 - Created)_
+
+```json
+{
+ "message": "Device berhasil ditambahkan ke favorit"
+}
+```
+
+_Response (400 - Bad Request)_
+
+```json
+{
+ "message": "Device sudah ada di daftar favorit Anda"
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "Device tidak ditemukan"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 11. GET /favorites
+
+Description:
+
+- Mendapatkan daftar device favorit user
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+[
+ {
+ "id": 1,
+ "key": "xiaomi-13-pro",
+ "device_name": "Xiaomi 13 Pro",
+ "device_image": "url_gambar",
+ "display_size": "6.73 inci",
+ "display_res": "1440 x 3200 pixel",
+ "camera": "50MP + 50MP + 50MP",
+ "video": "8K@24fps",
+ "ram": "12GB",
+ "chipset": "Snapdragon 8 Gen 2",
+ "battery": "4820 mAh",
+ "batteryType": "Li-Po",
+ "body": "Ceramic/Glass",
+ "os_type": "Android 13, MIUI 14",
+ "storage": "256GB/512GB",
+ "comment": "Flagship 2023",
+ "price": 12000000
+ }
+]
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## 12. DELETE /favorites/:XiaomiDeviceId
+
+Description:
+
+- Menghapus device dari daftar favorit
+
+Request:
+
+- headers:
+
+```json
+{
+ "access_token": "string"
+}
+```
+
+- params:
+
+```json
+{
+ "XiaomiDeviceId": "integer (required)"
+}
+```
+
+_Response (200 - OK)_
+
+```json
+{
+ "message": "Device berhasil dihapus dari favorit"
+}
+```
+
+_Response (404 - Not Found)_
+
+```json
+{
+ "message": "Device tidak ada di daftar favorit Anda"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
+
+
+
+## Global Error
+
+_Response (401 - Unauthorized)_
+
+```json
+{
+ "message": "Invalid token"
+}
+OR
+{
+ "message": "Authentication required"
+}
+```
+
+_Response (500 - Internal Server Error)_
+
+```json
+{
+ "message": "Internal server error"
+}
+```
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 00000000..a120b79f
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,25 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.env
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 00000000..7059a962
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,12 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/client/eslint.config.js b/client/eslint.config.js
new file mode 100644
index 00000000..ec2b712d
--- /dev/null
+++ b/client/eslint.config.js
@@ -0,0 +1,33 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+
+export default [
+ { ignores: ['dist'] },
+ {
+ files: ['**/*.{js,jsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ parserOptions: {
+ ecmaVersion: 'latest',
+ ecmaFeatures: { jsx: true },
+ sourceType: 'module',
+ },
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...js.configs.recommended.rules,
+ ...reactHooks.configs.recommended.rules,
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+]
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 00000000..9a352d35
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Vite + React
+
+
+
+
+
+
+
+
diff --git a/client/package-lock.json b/client/package-lock.json
new file mode 100644
index 00000000..695977f4
--- /dev/null
+++ b/client/package-lock.json
@@ -0,0 +1,3263 @@
+{
+ "name": "client",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "client",
+ "version": "0.0.0",
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.7.0",
+ "axios": "^1.9.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.5.3",
+ "sweetalert2": "^11.21.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.22.0",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.22.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.19",
+ "globals": "^16.0.0",
+ "vite": "^6.3.1"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.26.8",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
+ "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.26.10",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
+ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.0",
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.26.10",
+ "@babel/helper-compilation-targets": "^7.26.5",
+ "@babel/helper-module-transforms": "^7.26.0",
+ "@babel/helpers": "^7.26.10",
+ "@babel/parser": "^7.26.10",
+ "@babel/template": "^7.26.9",
+ "@babel/traverse": "^7.26.10",
+ "@babel/types": "^7.26.10",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
+ "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.26.8",
+ "@babel/helper-validator-option": "^7.25.9",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.26.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+ "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "@babel/traverse": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.26.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+ "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+ "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
+ "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz",
+ "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz",
+ "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
+ "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz",
+ "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz",
+ "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz",
+ "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz",
+ "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz",
+ "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz",
+ "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz",
+ "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz",
+ "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz",
+ "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz",
+ "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz",
+ "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz",
+ "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz",
+ "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz",
+ "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz",
+ "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz",
+ "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz",
+ "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz",
+ "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz",
+ "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz",
+ "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz",
+ "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz",
+ "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz",
+ "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz",
+ "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.6",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz",
+ "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
+ "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
+ "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",
+ "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.25.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz",
+ "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
+ "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
+ "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.13.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.6",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
+ "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
+ "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
+ "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@reduxjs/toolkit": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.7.0.tgz",
+ "integrity": "sha512-XVwolG6eTqwV0N8z/oDlN93ITCIGIop6leXlGJI/4EKy+0POYkR+ABHRSdGXY+0MQvJBP8yAzh+EYFxTuvmBiQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "@standard-schema/utils": "^0.3.0",
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.1.tgz",
+ "integrity": "sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.1.tgz",
+ "integrity": "sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.1.tgz",
+ "integrity": "sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.1.tgz",
+ "integrity": "sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.1.tgz",
+ "integrity": "sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.1.tgz",
+ "integrity": "sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.1.tgz",
+ "integrity": "sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.1.tgz",
+ "integrity": "sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.1.tgz",
+ "integrity": "sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.1.tgz",
+ "integrity": "sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.1.tgz",
+ "integrity": "sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.1.tgz",
+ "integrity": "sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.1.tgz",
+ "integrity": "sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.1.tgz",
+ "integrity": "sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.1.tgz",
+ "integrity": "sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.1.tgz",
+ "integrity": "sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.1.tgz",
+ "integrity": "sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.1.tgz",
+ "integrity": "sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.1.tgz",
+ "integrity": "sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.1.tgz",
+ "integrity": "sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
+ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.20.7",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz",
+ "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.20.7"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
+ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.2",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz",
+ "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.2",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz",
+ "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
+ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
+ "license": "MIT"
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
+ "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.26.10",
+ "@babel/plugin-transform-react-jsx-self": "^7.25.9",
+ "@babel/plugin-transform-react-jsx-source": "^7.25.9",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001715",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
+ "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.145",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.145.tgz",
+ "integrity": "sha512-pZ5EcTWRq/055MvSBgoFEyKf2i4apwfoqJbK/ak2jnFq8oHjZ+vzc3AhRcz37Xn+ZJfL58R666FLJx0YOK9yTw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
+ "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.3",
+ "@esbuild/android-arm": "0.25.3",
+ "@esbuild/android-arm64": "0.25.3",
+ "@esbuild/android-x64": "0.25.3",
+ "@esbuild/darwin-arm64": "0.25.3",
+ "@esbuild/darwin-x64": "0.25.3",
+ "@esbuild/freebsd-arm64": "0.25.3",
+ "@esbuild/freebsd-x64": "0.25.3",
+ "@esbuild/linux-arm": "0.25.3",
+ "@esbuild/linux-arm64": "0.25.3",
+ "@esbuild/linux-ia32": "0.25.3",
+ "@esbuild/linux-loong64": "0.25.3",
+ "@esbuild/linux-mips64el": "0.25.3",
+ "@esbuild/linux-ppc64": "0.25.3",
+ "@esbuild/linux-riscv64": "0.25.3",
+ "@esbuild/linux-s390x": "0.25.3",
+ "@esbuild/linux-x64": "0.25.3",
+ "@esbuild/netbsd-arm64": "0.25.3",
+ "@esbuild/netbsd-x64": "0.25.3",
+ "@esbuild/openbsd-arm64": "0.25.3",
+ "@esbuild/openbsd-x64": "0.25.3",
+ "@esbuild/sunos-x64": "0.25.3",
+ "@esbuild/win32-arm64": "0.25.3",
+ "@esbuild/win32-ia32": "0.25.3",
+ "@esbuild/win32-x64": "0.25.3"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.25.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz",
+ "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.20.0",
+ "@eslint/config-helpers": "^0.2.1",
+ "@eslint/core": "^0.13.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.25.1",
+ "@eslint/plugin-kit": "^0.2.8",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.3.0",
+ "eslint-visitor-keys": "^4.2.0",
+ "espree": "^10.3.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
+ "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
+ "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
+ "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz",
+ "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/immer"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
+ "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
+ "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-redux": {
+ "version": "9.2.0",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
+ "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/use-sync-external-store": "^0.0.6",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.2.25 || ^19",
+ "react": "^18.0 || ^19",
+ "redux": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "redux": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz",
+ "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0",
+ "turbo-stream": "2.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
+ "license": "MIT"
+ },
+ "node_modules/redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": "^5.0.0"
+ }
+ },
+ "node_modules/reselect": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.40.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.1.tgz",
+ "integrity": "sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.7"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.40.1",
+ "@rollup/rollup-android-arm64": "4.40.1",
+ "@rollup/rollup-darwin-arm64": "4.40.1",
+ "@rollup/rollup-darwin-x64": "4.40.1",
+ "@rollup/rollup-freebsd-arm64": "4.40.1",
+ "@rollup/rollup-freebsd-x64": "4.40.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.40.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.40.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.40.1",
+ "@rollup/rollup-linux-arm64-musl": "4.40.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.40.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.40.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.40.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.40.1",
+ "@rollup/rollup-linux-x64-gnu": "4.40.1",
+ "@rollup/rollup-linux-x64-musl": "4.40.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.40.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.40.1",
+ "@rollup/rollup-win32-x64-msvc": "4.40.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sweetalert2": {
+ "version": "11.21.0",
+ "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.21.0.tgz",
+ "integrity": "sha512-fiEK7SqRY/QD/wC2uqEHlfYGZ7qe2UcyQbJpbpj4YRVqplBgcI+euPZLZL+evLINcvbtXmL1SFUdZHKqBHGAAQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/limonte"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/turbo-stream": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
+ "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
+ "license": "ISC"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz",
+ "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 00000000..73dd52cb
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.7.0",
+ "axios": "^1.9.0",
+ "prop-types": "^15.8.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.5.3",
+ "sweetalert2": "^11.21.0"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.22.0",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
+ "@vitejs/plugin-react": "^4.3.4",
+ "eslint": "^9.22.0",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.19",
+ "globals": "^16.0.0",
+ "vite": "^6.3.1"
+ }
+}
diff --git a/client/public/vite.svg b/client/public/vite.svg
new file mode 100644
index 00000000..e7b8dfb1
--- /dev/null
+++ b/client/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/App.css b/client/src/App.css
new file mode 100644
index 00000000..b9d355df
--- /dev/null
+++ b/client/src/App.css
@@ -0,0 +1,42 @@
+#root {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+ filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a:nth-of-type(2) .logo {
+ animation: logo-spin infinite 20s linear;
+ }
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/client/src/App.jsx b/client/src/App.jsx
new file mode 100644
index 00000000..1d21b6c7
--- /dev/null
+++ b/client/src/App.jsx
@@ -0,0 +1,42 @@
+import { BrowserRouter, Routes, Route, Navigate, Outlet } from "react-router";
+import HomePage from "./pages/home.page";
+import LandingPage from "./pages/landing.page";
+import RegisterPage from "./pages/register.app";
+import LoginPage from "./pages/login.page";
+import DetailPage from "./pages/detail.page";
+import FavoritePage from "./pages/favorite.page";
+import Navbar from "./components/Navbar";
+
+function ProtectedRoute() {
+ const access_token = localStorage.getItem("access_token");
+ if (!access_token) {
+ return ;
+ }
+ return (
+ <>
+
+ >
+ );
+}
+
+function App() {
+ return (
+
+
+ {/* Public Routes */}
+ } />
+ } />
+ } />
+ } />
+
+ {/* Protected Routes */}
+ }>
+ } />
+ } />
+
+
+
+ );
+}
+
+export default App;
diff --git a/client/src/assets/react.svg b/client/src/assets/react.svg
new file mode 100644
index 00000000..6c87de9b
--- /dev/null
+++ b/client/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/components/AllDevices.jsx b/client/src/components/AllDevices.jsx
new file mode 100644
index 00000000..ddbd6ef8
--- /dev/null
+++ b/client/src/components/AllDevices.jsx
@@ -0,0 +1,31 @@
+import Card from "./card";
+import LoadingSpinner from "./LoadingSpinner";
+
+export default function AllDevices({ devices, loading, onCardClick }) {
+ return (
+
+ Daftar Lengkap HP
+ {loading ? (
+
+ ) : (
+
+ {devices.map((device) => (
+
+ onCardClick(device.id)}
+ />
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/components/AnimatedBackground.jsx b/client/src/components/AnimatedBackground.jsx
new file mode 100644
index 00000000..ff478281
--- /dev/null
+++ b/client/src/components/AnimatedBackground.jsx
@@ -0,0 +1,10 @@
+export default function AnimatedBackground() {
+ return (
+
+ );
+}
diff --git a/client/src/components/ErrorMessage.jsx b/client/src/components/ErrorMessage.jsx
new file mode 100644
index 00000000..9fd99096
--- /dev/null
+++ b/client/src/components/ErrorMessage.jsx
@@ -0,0 +1,10 @@
+export default function ErrorMessage({ message }) {
+ return (
+
+ );
+}
diff --git a/client/src/components/FeaturedDevices.jsx b/client/src/components/FeaturedDevices.jsx
new file mode 100644
index 00000000..b90cb11b
--- /dev/null
+++ b/client/src/components/FeaturedDevices.jsx
@@ -0,0 +1,50 @@
+import Card from "./card";
+import LoadingSpinner from "./LoadingSpinner";
+import { useState, useEffect } from "react";
+
+export default function FeaturedDevices({ devices, loading, onCardClick }) {
+ const [aiResponse, setAiResponse] = useState(null);
+
+ useEffect(() => {
+ try {
+ const storedResponse = localStorage.getItem("aiResponse");
+ if (storedResponse) {
+ setAiResponse(JSON.parse(storedResponse));
+ }
+ } catch (error) {
+ console.error("Error parsing AI response from localStorage:", error);
+ }
+ }, []);
+
+ return (
+
+
+ {aiResponse
+ ? `Rekomendasi AI: ${aiResponse}`
+ : "Berikut beberapa pilihan HP yang sangat cocok sesuai kebutuhan kamu..."}
+
+ {loading ? (
+
+ ) : (
+
+ {devices.map((device) => (
+
+ onCardClick(device.id)}
+ />
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/components/HomeStyles.jsx b/client/src/components/HomeStyles.jsx
new file mode 100644
index 00000000..5c8330b5
--- /dev/null
+++ b/client/src/components/HomeStyles.jsx
@@ -0,0 +1,265 @@
+export default function HomeStyles() {
+ return (
+ <>
+
+
+ Xiaomi Phone Catalog
+
+
+
+ >
+ );
+}
diff --git a/client/src/components/LandingStyles.jsx b/client/src/components/LandingStyles.jsx
new file mode 100644
index 00000000..38b94396
--- /dev/null
+++ b/client/src/components/LandingStyles.jsx
@@ -0,0 +1,73 @@
+export default function LandingStyles() {
+ return (
+ <>
+ {" "}
+ {" "}
+ {" "}
+ Selamat Datang di My Phones{" "}
+ {" "}
+ {" "}
+
+ >
+ );
+}
diff --git a/client/src/components/LoadingSpinner.jsx b/client/src/components/LoadingSpinner.jsx
new file mode 100644
index 00000000..b3da2eca
--- /dev/null
+++ b/client/src/components/LoadingSpinner.jsx
@@ -0,0 +1,9 @@
+export default function LoadingSpinner() {
+ return (
+
+ );
+}
diff --git a/client/src/components/Navbar.jsx b/client/src/components/Navbar.jsx
new file mode 100644
index 00000000..69e9e61b
--- /dev/null
+++ b/client/src/components/Navbar.jsx
@@ -0,0 +1,87 @@
+import { Link } from "react-router";
+import { useState, useEffect } from "react";
+import { useNavigate } from "react-router";
+import Swal from "sweetalert2";
+
+export default function Navbar() {
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ // Check for access_token in localStorage
+ const token = localStorage.getItem("access_token");
+ setIsLoggedIn(!!token);
+ }, []);
+
+ const handleLogout = () => {
+ // Tambahkan SweetAlert untuk konfirmasi logout
+ Swal.fire({
+ title: "Logout",
+ text: "Apakah Anda yakin ingin keluar?",
+ icon: "question",
+ showCancelButton: true,
+ confirmButtonText: "Ya",
+ cancelButtonText: "Tidak",
+ }).then((result) => {
+ if (result.isConfirmed) {
+ // Remove token from localStorage
+ localStorage.removeItem("access_token");
+ setIsLoggedIn(false);
+
+ // Tambahkan SweetAlert untuk logout berhasil
+ Swal.fire({
+ title: "Berhasil!",
+ text: "Anda telah berhasil logout",
+ icon: "success",
+ timer: 1500,
+ showConfirmButton: false,
+ });
+
+ navigate("/login");
+ }
+ });
+ };
+
+ return (
+
+ );
+}
diff --git a/client/src/components/WelcomeContent.jsx b/client/src/components/WelcomeContent.jsx
new file mode 100644
index 00000000..912a4812
--- /dev/null
+++ b/client/src/components/WelcomeContent.jsx
@@ -0,0 +1,54 @@
+import { useNavigate } from "react-router";
+import { useState } from "react";
+
+export default function WelcomeContent({ onAiRequest, loading, error }) {
+ const navigate = useNavigate();
+ const [prompt, setPrompt] = useState("");
+
+ const handleClick = () => {
+ if (prompt.trim()) {
+ onAiRequest(prompt);
+ setTimeout(() => {
+ navigate("/");
+ }, 2000);
+ }
+ };
+
+ return (
+
+
+
+ SELAMAT DATANG DI MY PHONES
+
+
+
+ Kami akan bantu cari handphone yang paling cocok sesuai kebutuhan
+ kamu...
+
+
+
+
+
+
+
+ {error &&
{error}
}
+
+
+
+ );
+}
diff --git a/client/src/components/card.jsx b/client/src/components/card.jsx
new file mode 100644
index 00000000..685aed2c
--- /dev/null
+++ b/client/src/components/card.jsx
@@ -0,0 +1,82 @@
+import { useState } from "react";
+import { Link } from "react-router";
+
+export default function Card({
+ title,
+ imageUrl,
+ isFeatured,
+ badgeText,
+ onClick,
+ price,
+}) {
+ const [imageError, setImageError] = useState(false);
+
+ // Handle image loading error
+ const handleImageError = () => {
+ setImageError(true);
+ };
+
+ // Format price as Indonesian Rupiah
+ const formatPrice = (price) => {
+ return new Intl.NumberFormat("id-ID", {
+ style: "currency",
+ currency: "IDR",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ }).format(price);
+ };
+
+ return (
+
+ {isFeatured &&
{badgeText}}
+
+

+
+
+
+ {title.length > 20 ? `${title.substring(0, 20)}...` : title}
+
+
+ {price && (
+
{formatPrice(price)}
+ )}
+
+
+
+ );
+}
+
+Card.defaultProps = {
+ imageUrl: "https://via.placeholder.com/300x200",
+ isFeatured: false,
+ badgeText: "NEW",
+ onClick: () => {},
+ price: null,
+};
diff --git a/client/src/index.css b/client/src/index.css
new file mode 100644
index 00000000..08a3ac9e
--- /dev/null
+++ b/client/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/client/src/main.jsx b/client/src/main.jsx
new file mode 100644
index 00000000..80d573fd
--- /dev/null
+++ b/client/src/main.jsx
@@ -0,0 +1,13 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { Provider } from "react-redux";
+import App from "./App.jsx";
+import store from "./store";
+
+createRoot(document.getElementById("root")).render(
+
+
+
+
+
+);
diff --git a/client/src/pages/detail.page.jsx b/client/src/pages/detail.page.jsx
new file mode 100644
index 00000000..d99302ae
--- /dev/null
+++ b/client/src/pages/detail.page.jsx
@@ -0,0 +1,393 @@
+import { useState, useEffect } from "react";
+import { useParams, useNavigate } from "react-router";
+import { useSelector, useDispatch } from "react-redux";
+import { toggleFavorite } from "../store/favoriteSlice";
+import axios from "axios";
+import Navbar from "../components/Navbar";
+
+export default function DetailPage() {
+ const { id } = useParams();
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const { favorites, loading: favoriteLoading } = useSelector(
+ (state) => state.favorites
+ );
+ const [device, setDevice] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const isFavorite = favorites.some((fav) => fav.id === Number(id));
+
+ useEffect(() => {
+ const fetchDeviceDetail = async () => {
+ try {
+ setLoading(true);
+ const token = localStorage.getItem("access_token");
+ if (token) {
+ setIsAuthenticated(true);
+ const response = await axios.get(
+ `http://localhost:3000/public/devices/${id}`,
+ {
+ headers: { access_token: token },
+ }
+ );
+ setDevice(response.data);
+ } else {
+ const response = await axios.get(
+ `http://localhost:3000/public/devices/${id}`
+ );
+ setDevice(response.data);
+ }
+ setLoading(false);
+ } catch (err) {
+ setError("Gagal mengambil data detail HP");
+ setLoading(false);
+ console.error("Error fetching device detail:", err);
+ }
+ };
+
+ fetchDeviceDetail();
+ }, [id]);
+
+ const handleBackClick = () => {
+ navigate(-1);
+ };
+
+ const handleToggleFavorite = () => {
+ if (!isAuthenticated) {
+ navigate("/login");
+ return;
+ }
+ dispatch(toggleFavorite(id));
+ };
+
+ const formatPrice = (price) => {
+ return new Intl.NumberFormat("id-ID", {
+ style: "currency",
+ currency: "IDR",
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0,
+ }).format(price);
+ };
+
+ return (
+ <>
+
+
+ {device ? `${device.device_name} - Detail` : "Loading..."}
+
+
+
+
+
+
+
+ {error && (
+
+
+ {error}
+
+ )}
+
+ {loading || favoriteLoading ? (
+
+ ) : (
+ device && (
+
+
+
+

{
+ e.target.src =
+ "https://via.placeholder.com/300x400?text=No+Image";
+ }}
+ />
+
+
+
+ {isAuthenticated && (
+
+ )}
+
+
+
+
+
{device.device_name}
+
{formatPrice(device.price)}
+
+
+
+
+
+ Spesifikasi Utama
+
+
+ {device.display_size && (
+ <>
+
Layar
+
+ {device.display_size}
+
+ >
+ )}
+
+ {device.display_res && (
+ <>
+
Resolusi Layar
+
{device.display_res}
+ >
+ )}
+
+ {device.camera && (
+ <>
+
Kamera
+
{device.camera}
+ >
+ )}
+
+ {device.chipset && (
+ <>
+
Chipset
+
{device.chipset}
+ >
+ )}
+
+ {device.ram && (
+ <>
+
RAM
+
{device.ram}
+ >
+ )}
+
+ {device.storage && (
+ <>
+
Penyimpanan
+
{device.storage}
+ >
+ )}
+
+
+
+
+
+
+ Detail Tambahan
+
+
+ {device.battery && (
+ <>
+
Baterai
+
{device.battery}
+ >
+ )}
+
+ {device.batteryType && (
+ <>
+
Tipe Baterai
+
{device.batteryType}
+ >
+ )}
+
+ {device.os_type && (
+ <>
+
Sistem Operasi
+
{device.os_type}
+ >
+ )}
+
+ {device.body && (
+ <>
+
Bahan Body
+
{device.body}
+ >
+ )}
+
+ {device.video && (
+ <>
+
Rekaman Video
+
{device.video}
+ >
+ )}
+
+
+
+
+ {device.comment && (
+
+
Deskripsi
+
{device.comment}
+
+ )}
+
+
+ )
+ )}
+
+ >
+ );
+}
diff --git a/client/src/pages/favorite.page.jsx b/client/src/pages/favorite.page.jsx
new file mode 100644
index 00000000..ca430b41
--- /dev/null
+++ b/client/src/pages/favorite.page.jsx
@@ -0,0 +1,77 @@
+import { useEffect } from "react";
+import { useNavigate } from "react-router";
+import { useSelector, useDispatch } from "react-redux";
+import { fetchFavorites, clearError } from "../store/favoriteSlice";
+import Navbar from "../components/Navbar";
+import Card from "../components/card";
+import LoadingSpinner from "../components/LoadingSpinner";
+import ErrorMessage from "../components/ErrorMessage";
+import HomeStyles from "../components/HomeStyles";
+
+export default function FavoritePage() {
+ const { favorites, loading, error } = useSelector((state) => state.favorites);
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const token = localStorage.getItem("access_token");
+ if (!token) {
+ navigate("/login");
+ return;
+ }
+ dispatch(fetchFavorites());
+ }, [dispatch, navigate]);
+
+ useEffect(() => {
+ if (error) {
+ const timer = setTimeout(() => {
+ dispatch(clearError());
+ }, 5000);
+ return () => clearTimeout(timer);
+ }
+ }, [error, dispatch]);
+
+ const handleCardClick = (phoneId) => {
+ if (phoneId) {
+ navigate(`/devices/${phoneId}`);
+ }
+ };
+
+ return (
+ <>
+
+
+
+
Daftar HP Favorit
+ {error &&
}
+ {loading ? (
+
+ ) : favorites.length === 0 ? (
+
+ Anda belum memiliki HP favorit. Kembali ke{" "}
+
halaman utama untuk menambahkan.
+
+ ) : (
+
+ {favorites.map((favorite, index) => (
+
+ handleCardClick(favorite.id)}
+ />
+
+ ))}
+
+ )}
+
+ >
+ );
+}
diff --git a/client/src/pages/home.page.jsx b/client/src/pages/home.page.jsx
new file mode 100644
index 00000000..352ea8c8
--- /dev/null
+++ b/client/src/pages/home.page.jsx
@@ -0,0 +1,79 @@
+import { useState, useEffect } from "react";
+import axios from "axios";
+import { useNavigate } from "react-router";
+import Navbar from "../components/Navbar";
+import ErrorMessage from "../components/ErrorMessage";
+import FeaturedDevices from "../components/FeaturedDevices";
+import AllDevices from "../components/AllDevices";
+import HomeStyles from "../components/HomeStyles";
+import WelcomeContent from "../components/WelcomeContent";
+
+export default function HomePage() {
+ const [devices, setDevices] = useState([]);
+ const [featuredDevices, setFeaturedDevices] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const fetchDevices = async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const response = await axios.get(
+ "http://localhost:3000/public/devices",
+ {
+ timeout: 5000,
+ }
+ );
+
+ setDevices(response.data);
+
+ const shuffledDevices = response.data.sort(() => 0.5 - Math.random());
+ setFeaturedDevices(shuffledDevices.slice(0, 4));
+
+ setLoading(false);
+ } catch (err) {
+ if (err.code === "ERR_NETWORK" || err.code === "ECONNREFUSED") {
+ setError(
+ "Tidak dapat terhubung ke server. Pastikan server berjalan di http://localhost:3000."
+ );
+ } else {
+ setError(
+ "Gagal mengambil data dari server: " +
+ (err.message || "Unknown error")
+ );
+ }
+ setLoading(false);
+ console.error("Error fetching devices:", err);
+ }
+ };
+
+ fetchDevices();
+ }, []);
+
+ const handleCardClick = (phoneId) => {
+ navigate(`/devices/${phoneId}`);
+ };
+
+ return (
+ <>
+
+
+
+ {error && }
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/pages/landing.page.jsx b/client/src/pages/landing.page.jsx
new file mode 100644
index 00000000..f5bd9dc1
--- /dev/null
+++ b/client/src/pages/landing.page.jsx
@@ -0,0 +1,61 @@
+import { useState } from "react";
+import AnimatedBackground from "../components/AnimatedBackground";
+import WelcomeContent from "../components/WelcomeContent";
+import LandingStyles from "../components/LandingStyles";
+import axios from "axios";
+
+export default function LandingPage() {
+ const [aiResponse, setAiResponse] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const handleAiRequest = async (prompt) => {
+ setLoading(true);
+ setError(null);
+ try {
+ const response = await axios.post(
+ "http://localhost:3000/ai",
+ { prompt },
+ {
+ timeout: 10000,
+ }
+ );
+
+ if (response.data && response.data.response) {
+ setAiResponse(response.data.response);
+ localStorage.setItem(
+ "aiResponse",
+ JSON.stringify(response.data.response)
+ );
+ } else {
+ throw new Error("Respons AI tidak valid");
+ }
+ } catch (err) {
+ if (err.code === "ERR_NETWORK" || err.code === "ECONNREFUSED") {
+ setError(
+ "Tidak dapat terhubung ke server. Pastikan server berjalan di http://localhost:3000."
+ );
+ } else {
+ setError(
+ "Gagal mendapatkan respons dari AI: " +
+ (err.message || "Unknown error")
+ );
+ }
+ console.error("Error fetching AI response:", err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/client/src/pages/login.page.jsx b/client/src/pages/login.page.jsx
new file mode 100644
index 00000000..0f61f8d8
--- /dev/null
+++ b/client/src/pages/login.page.jsx
@@ -0,0 +1,226 @@
+import { Link, useNavigate, useLocation } from "react-router";
+import { useEffect, useState } from "react";
+import axios from "axios";
+import Swal from "sweetalert2";
+
+export default function LoginPage() {
+ const [formData, setFormData] = useState({ email: "", password: "" });
+ const [error, setError] = useState("");
+ const [success, setSuccess] = useState("");
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ // Handler untuk input form
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ };
+
+ // Login normal dengan email dan password
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError("");
+ setSuccess("");
+ try {
+ const response = await axios.post(
+ "http://localhost:3000/login",
+ formData
+ );
+ localStorage.setItem("access_token", response.data.access_token);
+ localStorage.setItem("userId", response.data.id);
+ Swal.fire({
+ title: "Berhasil!",
+ text: "Login berhasil",
+ icon: "success",
+ timer: 1500,
+ showConfirmButton: false,
+ });
+ setTimeout(() => {
+ navigate("/");
+ }, 1500);
+ } catch (err) {
+ if (err.response) {
+ setError(
+ err.response.data.message || "Login failed. Please try again."
+ );
+ Swal.fire({
+ title: "Gagal!",
+ text: err.response.data.message || "Login gagal. Silakan coba lagi.",
+ icon: "error",
+ confirmButtonText: "OK",
+ });
+ } else {
+ setError("Network error. Please check your connection.");
+ Swal.fire({
+ title: "Error!",
+ text: "Network error. Silakan periksa koneksi internet Anda.",
+ icon: "error",
+ confirmButtonText: "OK",
+ });
+ }
+ }
+ };
+
+ // Login dengan Google
+ async function handleCredentialResponse(response) {
+ try {
+ const { data } = await axios.post("http://localhost:3000/login/google", {
+ googleToken: response.credential,
+ });
+ localStorage.setItem("access_token", data.access_token);
+ Swal.fire({
+ title: "Berhasil!",
+ text: "Login Google berhasil!",
+ icon: "success",
+ timer: 1500,
+ showConfirmButton: false,
+ });
+ setTimeout(() => {
+ navigate("/");
+ }, 1500);
+ } catch (err) {
+ setError(
+ err.response?.data.message || "Login Google gagal. Silakan coba lagi."
+ );
+ Swal.fire({
+ title: "Gagal!",
+ text:
+ err.response?.data.message ||
+ "Login Google gagal. Silakan coba lagi.",
+ icon: "error",
+ confirmButtonText: "OK",
+ });
+ }
+ }
+
+ // Fungsi untuk login dengan GitHub
+ const handleGitHubLogin = () => {
+ const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID;
+ window.location.href = `https://github.com/login/oauth/authorize?client_id=${clientId}&scope=user:email`;
+ };
+
+ // Inisialisasi Google login saat halaman dimuat
+ useEffect(() => {
+ window.google.accounts.id.initialize({
+ client_id: import.meta.env.VITE_REACT_APP_GOOGLE_CLIENT_ID,
+ callback: handleCredentialResponse,
+ });
+ window.google.accounts.id.renderButton(
+ document.getElementById("buttonDiv"),
+ { theme: "outline", size: "large" }
+ );
+ window.google.accounts.id.prompt();
+ }, []);
+
+ // Cek URL untuk parameter code dari GitHub
+ useEffect(() => {
+ const queryParams = new URLSearchParams(location.search);
+ const code = queryParams.get("code");
+ // Cegah eksekusi ganda dengan flag di sessionStorage
+ if (code && !sessionStorage.getItem("github_login_processing")) {
+ sessionStorage.setItem("github_login_processing", "true");
+ const githubLogin = async () => {
+ try {
+ setError("");
+ setSuccess("");
+ const response = await axios.post(
+ "http://localhost:3000/login/github",
+ { code }
+ );
+ localStorage.setItem("access_token", response.data.access_token);
+ localStorage.setItem("userId", response.data.id);
+ Swal.fire({
+ title: "Berhasil!",
+ text: "Login GitHub berhasil!",
+ icon: "success",
+ timer: 1500,
+ showConfirmButton: false,
+ }).then(() => {
+ sessionStorage.removeItem("github_login_processing");
+ navigate("/");
+ });
+ } catch (err) {
+ console.log(err);
+ sessionStorage.removeItem("github_login_processing");
+ Swal.fire({
+ title: "Gagal!",
+ text: "Login GitHub gagal. Silakan coba lagi.",
+ icon: "error",
+ confirmButtonText: "OK",
+ });
+ }
+ };
+ githubLogin();
+ }
+ }, [location.search, navigate]);
+ return (
+ <>
+
+
+ MY PHONES - Login
+ {/* Link Bootstrap Icons untuk icon GitHub */}
+
+
+
+
+ MY PHONES
+
+
Login
+ {error &&
{error}
}
+ {success &&
{success}
}
+
+
+
+
atau
+
+
+
+
+
+
+
+ Don't have an account yet? Register
+
+
+ >
+ );
+}
diff --git a/client/src/pages/register.app.jsx b/client/src/pages/register.app.jsx
new file mode 100644
index 00000000..e9b1f25b
--- /dev/null
+++ b/client/src/pages/register.app.jsx
@@ -0,0 +1,107 @@
+import { Link, useNavigate } from "react-router";
+import { useState } from "react";
+import axios from "axios";
+
+export default function RegisterPage() {
+ const [formData, setFormData] = useState({
+ username: "",
+ email: "",
+ password: "",
+ });
+ const [error, setError] = useState("");
+ const [success, setSuccess] = useState("");
+ const navigate = useNavigate();
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({ ...formData, [name]: value });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError("");
+ setSuccess("");
+
+ try {
+ const response = await axios.post(
+ "http://localhost:3000/register",
+ formData
+ );
+ setSuccess("Registration successful! Redirecting to login...");
+ setTimeout(() => {
+ navigate("/login");
+ }, 2000);
+ } catch (err) {
+ if (err.response) {
+ setError(
+ err.response.data.message || "Registration failed. Please try again."
+ );
+ } else {
+ setError("Network error. Please check your connection.");
+ }
+ }
+ };
+
+ return (
+ <>
+
+
+ MY PHONES - Register
+
+
+
+ MY PHONES
+
+
Register
+ {error &&
{error}
}
+ {success &&
{success}
}
+
+
+ Do you have an account? Login
+
+
+ >
+ );
+}
diff --git a/client/src/store/favoriteSlice.js b/client/src/store/favoriteSlice.js
new file mode 100644
index 00000000..072531ee
--- /dev/null
+++ b/client/src/store/favoriteSlice.js
@@ -0,0 +1,113 @@
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import axios from "axios";
+
+export const fetchFavorites = createAsyncThunk(
+ "favorites/fetchFavorites",
+ async (_, { rejectWithValue }) => {
+ try {
+ const token = localStorage.getItem("access_token");
+ const response = await axios.get("http://localhost:3000/favorites", {
+ headers: { access_token: token },
+ });
+ return Array.isArray(response.data) ? response.data : [];
+ } catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message || "Gagal mengambil data favorit"
+ );
+ }
+ }
+);
+
+export const toggleFavorite = createAsyncThunk(
+ "favorites/toggleFavorite",
+ async (id, { rejectWithValue }) => {
+ try {
+ const token = localStorage.getItem("access_token");
+ const favoriteResponse = await axios.get(
+ "http://localhost:3000/favorites",
+ {
+ headers: { access_token: token },
+ }
+ );
+ const isFavorite = favoriteResponse.data.some(
+ (fav) => fav.id === Number(id)
+ );
+
+ if (isFavorite) {
+ await axios.delete(`http://localhost:3000/favorites/${id}`, {
+ headers: { access_token: token },
+ });
+ return { id: Number(id), action: "remove" };
+ } else {
+ await axios.post(
+ `http://localhost:3000/favorites/${id}`,
+ {},
+ {
+ headers: { access_token: token },
+ }
+ );
+ const response = await axios.get(
+ `http://localhost:3000/public/devices/${id}`,
+ {
+ headers: { access_token: token },
+ }
+ );
+ return { id: Number(id), action: "add", device: response.data };
+ }
+ } catch (error) {
+ return rejectWithValue(
+ error.response?.data?.message || "Gagal memperbarui status favorit"
+ );
+ }
+ }
+);
+
+const favoriteSlice = createSlice({
+ name: "favorites",
+ initialState: {
+ favorites: [],
+ loading: false,
+ error: null,
+ },
+ reducers: {
+ clearError: (state) => {
+ state.error = null;
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(fetchFavorites.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(fetchFavorites.fulfilled, (state, action) => {
+ state.loading = false;
+ state.favorites = action.payload;
+ })
+ .addCase(fetchFavorites.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload;
+ })
+ .addCase(toggleFavorite.pending, (state) => {
+ state.loading = true;
+ state.error = null;
+ })
+ .addCase(toggleFavorite.fulfilled, (state, action) => {
+ state.loading = false;
+ if (action.payload.action === "remove") {
+ state.favorites = state.favorites.filter(
+ (fav) => fav.id !== action.payload.id
+ );
+ } else {
+ state.favorites.push(action.payload.device);
+ }
+ })
+ .addCase(toggleFavorite.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload;
+ });
+ },
+});
+
+export const { clearError } = favoriteSlice.actions;
+export default favoriteSlice.reducer;
diff --git a/client/src/store/index.js b/client/src/store/index.js
new file mode 100644
index 00000000..c285281c
--- /dev/null
+++ b/client/src/store/index.js
@@ -0,0 +1,10 @@
+import { configureStore } from "@reduxjs/toolkit";
+import favoriteReducer from "./favoriteSlice";
+
+const store = configureStore({
+ reducer: {
+ favorites: favoriteReducer,
+ },
+});
+
+export default store;
diff --git a/client/vite.config.js b/client/vite.config.js
new file mode 100644
index 00000000..9cc50ead
--- /dev/null
+++ b/client/vite.config.js
@@ -0,0 +1,7 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+});
diff --git a/server/.envexample b/server/.envexample
new file mode 100644
index 00000000..2a955b8a
--- /dev/null
+++ b/server/.envexample
@@ -0,0 +1,5 @@
+JWT_SECRET=""
+GOOGLE_CLIENT_ID=""
+GOOGLE_API_KEY=""
+GITHUB_CLIENT_ID=""
+GITHUB_CLIENT_SECRET=""
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 00000000..1dcef2d9
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+.env
\ No newline at end of file
diff --git a/server/__test__/test.sum.js b/server/__test__/test.sum.js
new file mode 100644
index 00000000..e69de29b
diff --git a/server/__test__/testing.js b/server/__test__/testing.js
new file mode 100644
index 00000000..e69de29b
diff --git a/server/__test__/testing.test.js b/server/__test__/testing.test.js
new file mode 100644
index 00000000..97763702
--- /dev/null
+++ b/server/__test__/testing.test.js
@@ -0,0 +1,801 @@
+const request = require("supertest");
+const express = require("express");
+const app = require("../app");
+const { sequelize } = require("../models");
+const { User, XiaomiDevice, Favorite } = require("../models");
+const { hashPassword } = require("../helpers/bcrypt");
+const { createToken } = require("../helpers/jwt");
+const { GoogleGenerativeAI } = require("@google/generative-ai");
+
+// Mock untuk GoogleGenerativeAI
+jest.mock("@google/generative-ai");
+
+let access_token;
+
+beforeAll(async () => {
+ try {
+ await Favorite.destroy({
+ truncate: true,
+ cascade: true,
+ restartIdentity: true,
+ });
+
+ await User.destroy({
+ truncate: true,
+ cascade: true,
+ restartIdentity: true,
+ });
+
+ await XiaomiDevice.destroy({
+ truncate: true,
+ cascade: true,
+ restartIdentity: true,
+ });
+
+ await User.create({
+ username: "testuser",
+ email: "test@example.com",
+ password: hashPassword("password123"),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ await XiaomiDevice.create({
+ key: "xiaomi-1",
+ device_name: "Xiaomi Test Phone",
+ device_image: "https://example.com/image.jpg",
+ display_size: "6.5 inches",
+ display_res: "1080 x 2400 pixels",
+ camera: "48MP + 8MP + 5MP",
+ video: "4K@30fps",
+ ram: "6GB",
+ chipset: "Snapdragon 732G",
+ battery: "5000mAh",
+ batteryType: "Li-Po",
+ body: "Plastic back, plastic frame",
+ os_type: "Android 11, MIUI 12",
+ storage: "128GB",
+ comment: "Excellent mid-range phone",
+ price: 3000000,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ const user = await User.findOne({ where: { email: "test@example.com" } });
+ access_token = createToken({ id: user.id, email: user.email });
+ } catch (error) {
+ console.error("Error in beforeAll:", error);
+ }
+});
+
+afterAll(async () => {
+ await sequelize.close();
+});
+
+describe("User Authentication", () => {
+ test("Register user baru dengan sukses", async () => {
+ const response = await request(app).post("/register").send({
+ username: "newuser",
+ email: "new@example.com",
+ password: "newpassword123",
+ });
+
+ expect(response.status).toBe(201);
+ expect(response.body).toHaveProperty("id");
+ expect(response.body).toHaveProperty("username", "newuser");
+ expect(response.body).toHaveProperty("email", "new@example.com");
+ });
+
+ test("Gagal register karena email sudah digunakan", async () => {
+ const response = await request(app).post("/register").send({
+ username: "duplicateuser",
+ email: "test@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty("message");
+ });
+
+ test("Gagal register karena validasi Sequelize", async () => {
+ const response = await request(app).post("/register").send({
+ username: "",
+ email: "invalid@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty("message");
+ });
+
+ test("Login dengan sukses", async () => {
+ const response = await request(app).post("/login").send({
+ email: "test@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("access_token");
+ expect(response.body).toHaveProperty("username", "testuser");
+ access_token = response.body.access_token;
+ });
+
+ test("Gagal login karena data tidak lengkap", async () => {
+ const response = await request(app).post("/login").send({
+ email: "test@example.com",
+ });
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Email and password are required"
+ );
+ });
+
+ test("Gagal login karena password salah", async () => {
+ const response = await request(app).post("/login").send({
+ email: "test@example.com",
+ password: "wrongpassword",
+ });
+
+ expect(response.status).toBe(401);
+ expect(response.body).toHaveProperty("message", "Invalid password");
+ });
+
+ test("Gagal login karena email tidak ditemukan", async () => {
+ const response = await request(app).post("/login").send({
+ email: "nonexistent@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "User not found");
+ });
+
+ test("Google login error handling", async () => {
+ const mockVerifyIdToken = jest
+ .fn()
+ .mockRejectedValue(new Error("Invalid token"));
+ const mockOAuth2Client = {
+ verifyIdToken: mockVerifyIdToken,
+ };
+
+ jest.mock("google-auth-library", () => ({
+ OAuth2Client: jest.fn().mockImplementation(() => mockOAuth2Client),
+ }));
+
+ const response = await request(app).post("/login/google").send({
+ googleToken: "invalid-token",
+ });
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+ });
+
+ test("Internal server error handling in register", async () => {
+ jest.spyOn(User, "create").mockImplementationOnce(() => {
+ throw new Error("Unexpected error");
+ });
+
+ const response = await request(app).post("/register").send({
+ username: "erroruser",
+ email: "error@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Internal server error handling in login", async () => {
+ jest.spyOn(User, "findOne").mockImplementationOnce(() => {
+ throw new Error("Unexpected error");
+ });
+
+ const response = await request(app).post("/login").send({
+ email: "test@example.com",
+ password: "password123",
+ });
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+});
+
+describe("Public Routes", () => {
+ test("Mendapatkan daftar device untuk public", async () => {
+ const response = await request(app).get("/public/devices");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ expect(response.body[0]).toHaveProperty("device_name");
+ expect(response.body[0]).toHaveProperty("price");
+ });
+
+ test("Mendapatkan daftar device dengan parameter query", async () => {
+ const response = await request(app).get(
+ "/public/devices?search=xiaomi&sort=price:asc&minPrice=1000000&maxPrice=5000000"
+ );
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ });
+
+ test("Mendapatkan daftar device dengan sort tidak valid", async () => {
+ const response = await request(app).get("/public/devices?sort=invalid:asc");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ });
+
+ test("Mendapatkan daftar device dengan parameter minPrice saja", async () => {
+ const response = await request(app).get("/public/devices?minPrice=1000000");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ });
+
+ test("Mendapatkan daftar device dengan parameter maxPrice saja", async () => {
+ const response = await request(app).get("/public/devices?maxPrice=5000000");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ });
+
+ test("Mendapatkan detail device untuk public", async () => {
+ const response = await request(app).get("/public/devices/1");
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("device_name");
+ expect(response.body).toHaveProperty("price");
+ expect(response.body).toHaveProperty("camera");
+ });
+
+ test("Gagal mendapatkan detail device yang tidak ada untuk public", async () => {
+ const response = await request(app).get("/public/devices/999");
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "Device tidak ditemukan");
+ });
+
+ test("Error handling pada public routes", async () => {
+ jest.spyOn(XiaomiDevice, "findAll").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app).get("/public/devices");
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Error handling pada get device by id public route", async () => {
+ jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app).get("/public/devices/1");
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+});
+
+describe("Authenticated Endpoints", () => {
+ test("Mendapatkan daftar device lengkap dengan sukses", async () => {
+ const response = await request(app)
+ .get("/devices")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ expect(response.body[0]).toHaveProperty("device_name");
+ expect(response.body[0]).toHaveProperty("camera");
+ });
+
+ test("Mendapatkan daftar device tanpa token", async () => {
+ const response = await request(app).get("/devices");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ expect(response.body[0]).toHaveProperty("device_name");
+ expect(response.body[0]).not.toHaveProperty("camera");
+ });
+
+ test("Error handling pada get devices authenticated route", async () => {
+ jest.spyOn(XiaomiDevice, "findAll").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .get("/devices")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Mendapatkan detail device lengkap dengan sukses", async () => {
+ const response = await request(app)
+ .get("/devices/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("device_name");
+ expect(response.body).toHaveProperty("camera");
+ });
+
+ test("Mendapatkan detail device tanpa token", async () => {
+ const response = await request(app).get("/devices/1");
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("device_name");
+ expect(response.body).not.toHaveProperty("camera");
+ });
+
+ test("Gagal mendapatkan detail device yang tidak ada", async () => {
+ const response = await request(app)
+ .get("/devices/999")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "Device tidak ditemukan");
+ });
+
+ test("Error handling pada get device by id authenticated route", async () => {
+ jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .get("/devices/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Menambahkan device ke favorit dengan sukses", async () => {
+ await Favorite.destroy({ where: { XiaomiDeviceId: 1 } });
+
+ const response = await request(app)
+ .post("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(201);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Device berhasil ditambahkan ke favorit"
+ );
+ });
+
+ test("Gagal menambahkan device ke favorit karena sudah ada", async () => {
+ const response = await request(app)
+ .post("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Device sudah ada di daftar favorit Anda"
+ );
+ });
+
+ test("Gagal menambahkan device yang tidak ada ke favorit", async () => {
+ const response = await request(app)
+ .post("/favorites/999")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "Device tidak ditemukan");
+ });
+
+ test("Error handling pada add to favorites route", async () => {
+ jest.spyOn(XiaomiDevice, "findByPk").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .post("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Mendapatkan daftar favorit dengan sukses", async () => {
+ const response = await request(app)
+ .get("/favorites")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ expect(response.body.length).toBeGreaterThan(0);
+ });
+
+ test("Error handling pada get favorites route", async () => {
+ jest.spyOn(Favorite, "findAll").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .get("/favorites")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Menghapus device dari favorit dengan sukses", async () => {
+ // Tambahkan kembali ke favorit untuk dihapus
+ await Favorite.create({
+ UserId: 1,
+ XiaomiDeviceId: 1,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ });
+
+ const response = await request(app)
+ .delete("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Device berhasil dihapus dari favorit"
+ );
+ });
+
+ test("Gagal menghapus device dari favorit karena tidak ada", async () => {
+ const response = await request(app)
+ .delete("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Device tidak ada di daftar favorit Anda"
+ );
+ });
+
+ test("Error handling pada remove from favorites route", async () => {
+ jest.spyOn(Favorite, "findOne").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .delete("/favorites/1")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Update user profile dengan sukses", async () => {
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({
+ username: "updateduser",
+ email: "updated@example.com",
+ });
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("message", "User berhasil diupdate");
+ expect(response.body.user).toHaveProperty("username", "updateduser");
+ expect(response.body.user).toHaveProperty("email", "updated@example.com");
+ });
+
+ test("Gagal update user profile tanpa data", async () => {
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({});
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Tidak ada data yang diupdate"
+ );
+ });
+
+ test("Gagal update user profile dengan user tidak ditemukan", async () => {
+ jest.spyOn(User, "findByPk").mockResolvedValueOnce(null);
+
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({
+ username: "newname",
+ });
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "User tidak ditemukan");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Gagal update user profile karena validasi Sequelize", async () => {
+ const validationError = {
+ name: "SequelizeValidationError",
+ errors: [{ message: "Username tidak boleh kosong" }],
+ };
+
+ jest.spyOn(User.prototype, "update").mockRejectedValueOnce(validationError);
+
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({
+ username: "",
+ });
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty(
+ "message",
+ "Username tidak boleh kosong"
+ );
+
+ jest.restoreAllMocks();
+ });
+
+ test("Gagal update user profile karena unique constraint Sequelize", async () => {
+ const uniqueError = {
+ name: "SequelizeUniqueConstraintError",
+ errors: [{ message: "Email sudah digunakan" }],
+ };
+
+ jest.spyOn(User.prototype, "update").mockRejectedValueOnce(uniqueError);
+
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({
+ email: "test@example.com",
+ });
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty("message", "Email sudah digunakan");
+
+ jest.restoreAllMocks();
+ });
+
+ test("Error handling pada update user route", async () => {
+ jest.spyOn(User, "findByPk").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .put("/users/update")
+ .set("access_token", access_token)
+ .send({
+ username: "newname",
+ });
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+
+ jest.restoreAllMocks();
+ });
+});
+
+describe("Authentication Middleware", () => {
+ test("Mengakses endpoint publik tanpa token", async () => {
+ const response = await request(app).get("/public/devices");
+
+ expect(response.status).toBe(200);
+ expect(Array.isArray(response.body)).toBe(true);
+ });
+
+ test("Gagal mengakses endpoint terproteksi dengan token invalid", async () => {
+ const response = await request(app)
+ .get("/favorites")
+ .set("access_token", "invalid-token");
+
+ expect(response.status).toBe(401);
+ expect(response.body).toHaveProperty("message", "Invalid token");
+ });
+
+ test("Gagal mengakses endpoint dengan user tidak ditemukan", async () => {
+ const invalidToken = createToken({
+ id: 999,
+ email: "nonexist@example.com",
+ });
+
+ const response = await request(app)
+ .get("/favorites")
+ .set("access_token", invalidToken);
+
+ expect(response.status).toBe(404);
+ expect(response.body).toHaveProperty("message", "User not found");
+ });
+
+ test("Error handling di middleware authentication", async () => {
+ jest.spyOn(User, "findByPk").mockImplementationOnce(() => {
+ throw new Error("Database error");
+ });
+
+ const response = await request(app)
+ .get("/favorites")
+ .set("access_token", access_token);
+
+ expect(response.status).toBe(401);
+ expect(response.body).toHaveProperty("message", "Invalid token");
+
+ jest.restoreAllMocks();
+ });
+});
+
+describe("AI Endpoint", () => {
+ beforeEach(() => {
+ // Mock untuk Google Generative AI
+ const mockModel = {
+ generateContent: jest.fn().mockResolvedValue({
+ response: {
+ text: () => "Ini adalah respons dari Gemini API",
+ },
+ }),
+ };
+
+ GoogleGenerativeAI.mockImplementation(() => ({
+ getGenerativeModel: jest.fn().mockReturnValue(mockModel),
+ }));
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ test("Mendapatkan respons AI dengan sukses", async () => {
+ const response = await request(app).post("/ai").send({
+ prompt: "Apa kelebihan HP Xiaomi?",
+ });
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("response");
+ });
+
+ test("Gagal mendapatkan respons AI tanpa prompt", async () => {
+ const response = await request(app).post("/ai").send({});
+
+ expect(response.status).toBe(400);
+ expect(response.body).toHaveProperty("message", "Prompt diperlukan");
+ });
+
+ test("Error handling pada AI endpoint", async () => {
+ GoogleGenerativeAI.mockImplementation(() => ({
+ getGenerativeModel: jest.fn().mockImplementation(() => {
+ throw new Error("API error");
+ }),
+ }));
+
+ const response = await request(app).post("/ai").send({
+ prompt: "Apa kelebihan HP Xiaomi?",
+ });
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+ });
+});
+
+describe("Testing Routes", () => {
+ test("Health check endpoint", async () => {
+ const response = await request(app).get("/testing/health");
+
+ expect(response.status).toBe(200);
+ expect(response.body).toHaveProperty("message", "API is running");
+ });
+});
+
+describe("Error Handler", () => {
+ test("Global error handler", async () => {
+ // Buat router express sederhana dengan error handler dari app.js
+ const router = express();
+ router.use((req, res, next) => {
+ next(new Error("Test error"));
+ });
+
+ // Ekstrak error handler dari app.js
+ const errorHandler = app._router.stack
+ .filter((layer) => layer.handle && layer.handle.length === 4)
+ .pop().handle;
+
+ // Tambahkan error handler ke router
+ router.use(errorHandler);
+
+ // Test
+ const response = await request(router).get("/");
+
+ expect(response.status).toBe(500);
+ expect(response.body).toHaveProperty("message", "Internal server error");
+ });
+});
+
+describe("App Error Handling", () => {
+ // Simulasi error ketika server dijalankan
+ test("Server error handler", () => {
+ const mockServer = {
+ listen: jest.fn().mockReturnThis(),
+ on: jest.fn().mockImplementation((event, callback) => {
+ if (event === "error") {
+ // Simulasi error EADDRINUSE
+ callback({ code: "EADDRINUSE" });
+ }
+ return mockServer;
+ }),
+ };
+
+ const originalListen = app.listen;
+ app.listen = jest.fn().mockReturnValue(mockServer);
+
+ const consoleSpy = jest
+ .spyOn(console, "error")
+ .mockImplementation(() => {});
+
+ // Jalankan aplikasi
+ const serverInstance = app.listen(3000, () => {});
+
+ // Verifikasi bahwa on("error") dipanggil
+ expect(serverInstance.on).toHaveBeenCalledWith(
+ "error",
+ expect.any(Function)
+ );
+ expect(consoleSpy).toHaveBeenCalled();
+
+ // Simulasi error lain
+ serverInstance.on.mock.calls[0][1]({ code: "OTHER_ERROR" });
+ expect(consoleSpy).toHaveBeenCalledTimes(2);
+
+ // Restore mocks
+ consoleSpy.mockRestore();
+ app.listen = originalListen;
+ });
+
+ test("Gemini API initialization error", () => {
+ // Backup environment variable
+ const originalApiKey = process.env.GOOGLE_API_KEY;
+
+ // Simulasikan environment variable tidak ada
+ delete process.env.GOOGLE_API_KEY;
+
+ const consoleSpy = jest
+ .spyOn(console, "error")
+ .mockImplementation(() => {});
+ const processExitSpy = jest
+ .spyOn(process, "exit")
+ .mockImplementation(() => {});
+
+ // Require app.js lagi untuk menjalankan code initialization
+ jest.isolateModules(() => {
+ try {
+ require("../app");
+ } catch (error) {
+ // Catch any errors
+ }
+ });
+
+ // Restore environment variable
+ process.env.GOOGLE_API_KEY = originalApiKey;
+
+ consoleSpy.mockRestore();
+ processExitSpy.mockRestore();
+ });
+});
diff --git a/server/app.js b/server/app.js
new file mode 100644
index 00000000..3e9cd991
--- /dev/null
+++ b/server/app.js
@@ -0,0 +1,115 @@
+if (process.env.NODE_ENV !== "production") {
+ require("dotenv").config();
+}
+
+const express = require("express");
+const app = express();
+const port = process.env.PORT || 3000;
+const cors = require("cors");
+const authentication = require("./middlewares/authentication");
+const UserController = require("./controllers/UserController");
+const Controller = require("./controllers/Controller");
+const PubController = require("./controllers/PubController");
+const testingRoutes = require("./routes/testing");
+const { GoogleGenerativeAI } = require("@google/generative-ai");
+
+// Pastikan API key tersedia
+if (!process.env.GOOGLE_API_KEY) {
+ console.error("GOOGLE_API_KEY tidak ditemukan di environment variables");
+ process.exit(1);
+}
+
+const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);
+
+async function geminiApi({ prompt }) {
+ try {
+ const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
+
+ // Modifikasi prompt untuk membatasi konteks ke HP Xiaomi saja
+ const enhancedPrompt = `
+ Kamu adalah asisten yang hanya membahas tentang HP Xiaomi.
+ Berikan jawaban hanya seputar HP Xiaomi saja.
+ Jangan pernah menyebutkan atau merekomendasikan merk smartphone lain atau gadget lain selain Xiaomi.
+ Jika ditanya tentang merk lain, arahkan pembicaraan kembali ke Xiaomi.
+
+ Fokuskan jawaban pada informasi spesifikasi, fitur, dan kelebihan HP Xiaomi sesuai dengan kebutuhan pengguna.
+ Berikan jawaban singkat, padat, dan informatif.
+
+ Pertanyaan: ${prompt}
+ `;
+
+ const result = await model.generateContent(enhancedPrompt);
+ const response = await result.response;
+ return response.text();
+ } catch (error) {
+ console.error("Error in geminiApi:", error);
+ throw error;
+ }
+}
+
+// Middleware
+app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+
+// Testing routes
+app.use("/testing", testingRoutes);
+
+// Route untuk public (tanpa login)
+app.get("/public/devices", PubController.getAllDevices);
+app.get("/public/devices/:id", PubController.getDeviceById);
+
+// Route untuk AI
+app.post("/ai", async (req, res) => {
+ try {
+ const { prompt } = req.body;
+ if (!prompt) {
+ return res.status(400).json({ message: "Prompt diperlukan" });
+ }
+ const response = await geminiApi({ prompt });
+ res.status(200).json({ response });
+ } catch (error) {
+ console.error("Error in AI route:", error);
+ res.status(500).json({ message: "Internal server error" });
+ }
+});
+
+// Route untuk user (login & register)
+app.post("/register", UserController.register);
+app.post("/login", UserController.login);
+app.post("/login/google", UserController.googleLogin);
+app.post("/login/github", UserController.githubLogin);
+
+// Middleware authentication untuk semua rute dibawah ini
+app.use(authentication);
+
+// Routes that require authentication
+app.get("/devices", Controller.getAllDevices);
+app.get("/devices/:id", Controller.getDeviceById);
+app.put("/users/update", UserController.updateUser);
+app.post("/favorites/:XiaomiDeviceId", Controller.addToFavorites);
+app.get("/favorites", Controller.getFavorites);
+app.delete("/favorites/:XiaomiDeviceId", Controller.removeFromFavorites);
+
+// Error handler
+app.use((err, req, res, next) => {
+ console.error("Global error:", err);
+ res.status(500).json({ message: "Internal server error" });
+});
+
+// Start the server with error handling
+app
+ .listen(port, () => {
+ console.log(`Server successfully running on port ${port}`);
+ })
+ .on("error", (err) => {
+ if (err.code === "EADDRINUSE") {
+ console.error(
+ `Port ${port} is already in use. Please use a different port or free up port ${port}.`
+ );
+ } else {
+ console.error("Failed to start server:", err);
+ }
+ });
+
+module.exports = app;
diff --git a/server/config/config.json b/server/config/config.json
new file mode 100644
index 00000000..43152391
--- /dev/null
+++ b/server/config/config.json
@@ -0,0 +1,23 @@
+{
+ "development": {
+ "username": "postgres",
+ "password": "qwerty",
+ "database": "list_hp_DB",
+ "host": "127.0.0.1",
+ "dialect": "postgres"
+ },
+ "test": {
+ "username": "postgres",
+ "password": "qwerty",
+ "database": "list_hp_DB_test",
+ "host": "127.0.0.1",
+ "dialect": "postgres"
+ },
+ "production": {
+ "username": "root",
+ "password": null,
+ "database": "database_production",
+ "host": "127.0.0.1",
+ "dialect": "mysql"
+ }
+}
diff --git a/server/controllers/Controller.js b/server/controllers/Controller.js
new file mode 100644
index 00000000..d17fe458
--- /dev/null
+++ b/server/controllers/Controller.js
@@ -0,0 +1,149 @@
+const { XiaomiDevice, User, Favorite } = require("../models");
+
+class Controller {
+ // Method untuk mendapatkan semua device Xiaomi
+ static async getAllDevices(req, res) {
+ try {
+ const devices = await XiaomiDevice.findAll();
+
+ // Mengecek apakah user sudah login atau belum
+ const isLoggedIn = req.user ? true : false;
+
+ // Jika user belum login, hanya kirim informasi terbatas
+ if (!isLoggedIn) {
+ const limitedDevices = devices.map((device) => {
+ return {
+ id: device.id,
+ key: device.key,
+ device_name: device.device_name,
+ device_image: device.device_image,
+ price: device.price,
+ };
+ });
+
+ return res.status(200).json(limitedDevices);
+ }
+
+ // Jika user sudah login, kirim semua informasi device
+ res.status(200).json(devices);
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ // Method untuk mendapatkan detail satu device berdasarkan ID
+ static async getDeviceById(req, res) {
+ try {
+ const { id } = req.params;
+
+ const device = await XiaomiDevice.findByPk(id);
+
+ if (!device) {
+ return res.status(404).json({ message: "Device tidak ditemukan" });
+ }
+
+ // Mengecek apakah user sudah login atau belum
+ const isLoggedIn = req.user ? true : false;
+
+ // Jika user belum login, hanya kirim informasi terbatas
+ if (!isLoggedIn) {
+ const limitedDevice = {
+ id: device.id,
+ key: device.key,
+ device_name: device.device_name,
+ device_image: device.device_image,
+ price: device.price,
+ };
+
+ return res.status(200).json(limitedDevice);
+ }
+
+ // Jika user sudah login, kirim semua informasi device
+ res.status(200).json(device);
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ // Method untuk menambahkan device ke daftar favorit
+ static async addToFavorites(req, res) {
+ try {
+ const UserId = req.user.id; // Diambil dari middleware authentication
+ const { XiaomiDeviceId } = req.params;
+
+ // Memastikan device ada
+ const device = await XiaomiDevice.findByPk(XiaomiDeviceId);
+
+ if (!device) {
+ return res.status(404).json({ message: "Device tidak ditemukan" });
+ }
+
+ // Mengecek apakah device sudah ada di daftar favorit user
+ const existingFavorite = await Favorite.findOne({
+ where: { UserId, XiaomiDeviceId },
+ });
+
+ if (existingFavorite) {
+ return res
+ .status(400)
+ .json({ message: "Device sudah ada di daftar favorit Anda" });
+ }
+
+ // Menambahkan device ke daftar favorit
+ await Favorite.create({ UserId, XiaomiDeviceId });
+
+ res
+ .status(201)
+ .json({ message: "Device berhasil ditambahkan ke favorit" });
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ // Method untuk mendapatkan daftar favorit user
+ static async getFavorites(req, res) {
+ try {
+ const UserId = req.user.id; // Add this line to get UserId from authenticated request
+
+ const favorites = await Favorite.findAll({
+ where: { UserId },
+ include: [XiaomiDevice],
+ });
+
+ const favoriteDevices = favorites.map((fav) => fav.XiaomiDevice);
+
+ res.status(200).json(favoriteDevices);
+ } catch (error) {
+ console.error(error); // Add logging for debugging
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ // Method untuk menghapus device dari daftar favorit
+ static async removeFromFavorites(req, res) {
+ try {
+ const UserId = req.user.id; // Diambil dari middleware authentication
+ const { XiaomiDeviceId } = req.params;
+
+ // Memastikan device ada di daftar favorit user
+ const favorite = await Favorite.findOne({
+ where: { UserId, XiaomiDeviceId },
+ });
+
+ if (!favorite) {
+ return res
+ .status(404)
+ .json({ message: "Device tidak ada di daftar favorit Anda" });
+ }
+
+ // Menghapus device dari daftar favorit
+ await favorite.destroy();
+
+ res.status(200).json({ message: "Device berhasil dihapus dari favorit" });
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+}
+
+module.exports = Controller;
diff --git a/server/controllers/PubController.js b/server/controllers/PubController.js
new file mode 100644
index 00000000..ebb81660
--- /dev/null
+++ b/server/controllers/PubController.js
@@ -0,0 +1,82 @@
+const {
+ XiaomiDevice,
+ Sequelize: { Op },
+} = require("../models");
+
+class PubController {
+ static async getAllDevices(req, res) {
+ try {
+ const { search, sort, minPrice, maxPrice } = req.query;
+
+ // Build the query options
+ let queryOptions = {};
+
+ // Search functionality
+ if (search) {
+ queryOptions.where = {
+ [Op.or]: [
+ { device_name: { [Op.iLike]: `%${search}%` } },
+ { key: { [Op.iLike]: `%${search}%` } },
+ ],
+ };
+ }
+
+ // Price filter
+ if (minPrice || maxPrice) {
+ queryOptions.where = {
+ ...queryOptions.where,
+ price: {},
+ };
+ if (minPrice) queryOptions.where.price[Op.gte] = minPrice;
+ if (maxPrice) queryOptions.where.price[Op.lte] = maxPrice;
+ }
+
+ // Sort functionality
+ if (sort) {
+ const [field, order] = sort.split(":");
+ const validFields = ["price", "device_name"];
+ const validOrders = ["asc", "desc"];
+
+ if (validFields.includes(field) && validOrders.includes(order)) {
+ queryOptions.order = [[field, order.toUpperCase()]];
+ }
+ }
+
+ const devices = await XiaomiDevice.findAll(queryOptions);
+
+ // Map to limited device info
+ const limitedDevices = devices.map((device) => ({
+ id: device.id,
+ key: device.key,
+ device_name: device.device_name,
+ device_image: device.device_image,
+ price: device.price,
+ }));
+
+ res.status(200).json(limitedDevices);
+ } catch (error) {
+ console.error("Error in getAllDevices:", error);
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ // Method untuk mendapatkan detail satu device berdasarkan ID dengan informasi lengkap
+ static async getDeviceById(req, res) {
+ try {
+ const { id } = req.params;
+
+ const device = await XiaomiDevice.findByPk(id);
+
+ if (!device) {
+ return res.status(404).json({ message: "Device tidak ditemukan" });
+ }
+
+ // Kirim semua informasi device
+ res.status(200).json(device);
+ } catch (error) {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+}
+
+module.exports = PubController;
diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js
new file mode 100644
index 00000000..9e2f942e
--- /dev/null
+++ b/server/controllers/UserController.js
@@ -0,0 +1,254 @@
+const { User } = require("../models");
+const { comparePassword } = require("../helpers/bcrypt");
+const { createToken } = require("../helpers/jwt");
+const { OAuth2Client } = require("google-auth-library");
+const axios = require("axios"); // Untuk GitHub login
+const client = new OAuth2Client();
+
+class UserController {
+ static async googleLogin(req, res) {
+ try {
+ const { googleToken } = req.body;
+ const ticket = await client.verifyIdToken({
+ idToken: googleToken,
+ audience: process.env.GOOGLE_CLIENT_ID,
+ });
+ const payload = ticket.getPayload();
+
+ let user = await User.findOne({
+ where: { email: payload.email },
+ });
+
+ if (!user) {
+ user = await User.create({
+ username: payload.name,
+ email: payload.email,
+ password: payload.sub,
+ });
+ }
+
+ const tokenPayload = {
+ id: user.id,
+ email: user.email,
+ username: user.username,
+ };
+
+ const token = createToken(tokenPayload);
+ res.status(200).json({
+ access_token: token,
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ static async githubLogin(req, res) {
+ try {
+ const { code } = req.body;
+
+ // Step 1: Tukar kode otorisasi dengan access token
+ const tokenResponse = await axios.post(
+ "https://github.com/login/oauth/access_token",
+ {
+ client_id: process.env.GITHUB_CLIENT_ID,
+ client_secret: process.env.GITHUB_CLIENT_SECRET,
+ code,
+ },
+ {
+ headers: {
+ Accept: "application/json",
+ },
+ }
+ );
+
+ const { access_token } = tokenResponse.data;
+
+ // Step 2: Gunakan access token untuk mendapatkan informasi user
+ const userResponse = await axios.get("https://api.github.com/user", {
+ headers: {
+ Authorization: `token ${access_token}`,
+ },
+ });
+
+ const githubUser = userResponse.data;
+
+ // Step 3: Email tidak selalu tersedia di GitHub API, coba ambil email
+ let email;
+ try {
+ const emailsResponse = await axios.get(
+ "https://api.github.com/user/emails",
+ {
+ headers: {
+ Authorization: `token ${access_token}`,
+ },
+ }
+ );
+
+ // Cari email yang primary dan verified
+ const primaryEmail = emailsResponse.data.find(
+ (email) => email.primary && email.verified
+ );
+ email = primaryEmail
+ ? primaryEmail.email
+ : emailsResponse.data[0].email;
+ } catch (error) {
+ // Jika tidak bisa mendapatkan email, gunakan fallback
+ email = `${githubUser.login}@github.com`;
+ }
+
+ // Step 4: Cari user dengan email tersebut atau buat user baru
+ let user = await User.findOne({
+ where: { email },
+ });
+
+ if (!user) {
+ user = await User.create({
+ username: githubUser.name || githubUser.login,
+ email,
+ password: githubUser.id.toString(), // Gunakan id GitHub sebagai password
+ });
+ }
+
+ // Step 5: Buat token JWT
+ const tokenPayload = {
+ id: user.id,
+ email: user.email,
+ username: user.username,
+ };
+
+ const token = createToken(tokenPayload);
+
+ res.status(200).json({
+ access_token: token,
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ });
+ } catch (error) {
+ console.error("GitHub login error:", error);
+ res.status(500).json({ message: "Gagal login menggunakan GitHub" });
+ }
+ }
+
+ static async register(req, res) {
+ try {
+ const { username, email, password } = req.body;
+
+ const newUser = await User.create({
+ username,
+ email,
+ password,
+ });
+
+ res.status(201).json({
+ id: newUser.id,
+ username: newUser.username,
+ email: newUser.email,
+ });
+ } catch (error) {
+ if (
+ error.name === "SequelizeValidationError" ||
+ error.name === "SequelizeUniqueConstraintError"
+ ) {
+ res.status(400).json({ message: error.errors[0].message });
+ } else {
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+ }
+
+ static async login(req, res) {
+ try {
+ const { email, password } = req.body;
+
+ if (!email || !password) {
+ return res
+ .status(400)
+ .json({ message: "Email and password are required" });
+ }
+
+ const user = await User.findOne({
+ where: { email },
+ });
+
+ if (!user) {
+ return res.status(404).json({ message: "User not found" });
+ }
+
+ const isPasswordValid = comparePassword(password, user.password);
+
+ if (!isPasswordValid) {
+ return res.status(401).json({ message: "Invalid password" });
+ }
+
+ const payload = {
+ id: user.id,
+ email: user.email,
+ username: user.username,
+ };
+
+ const token = createToken(payload);
+
+ res.status(200).json({
+ access_token: token,
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ });
+ } catch (error) {
+ console.error(error);
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+
+ static async updateUser(req, res) {
+ try {
+ const userId = req.user.id;
+ const { username, email } = req.body;
+
+ const user = await User.findByPk(userId);
+
+ if (!user) {
+ return res.status(404).json({ message: "User tidak ditemukan" });
+ }
+
+ const updateFields = {};
+
+ if (username) updateFields.username = username;
+ if (email) updateFields.email = email;
+
+ if (Object.keys(updateFields).length === 0) {
+ return res
+ .status(400)
+ .json({ message: "Tidak ada data yang diupdate" });
+ }
+
+ await user.update(updateFields);
+
+ res.status(200).json({
+ message: "User berhasil diupdate",
+ user: {
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ },
+ });
+ } catch (error) {
+ if (
+ error.name === "SequelizeValidationError" ||
+ error.name === "SequelizeUniqueConstraintError"
+ ) {
+ res.status(400).json({ message: error.errors[0].message });
+ } else {
+ console.error(error);
+ res.status(500).json({ message: "Internal server error" });
+ }
+ }
+ }
+}
+
+module.exports = UserController;
diff --git a/server/coverage/clover.xml b/server/coverage/clover.xml
new file mode 100644
index 00000000..e69c9143
--- /dev/null
+++ b/server/coverage/clover.xml
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/coverage/coverage-final.json b/server/coverage/coverage-final.json
new file mode 100644
index 00000000..4957fcff
--- /dev/null
+++ b/server/coverage/coverage-final.json
@@ -0,0 +1,13 @@
+{"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\app.js","statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":29}},"2":{"start":{"line":5,"column":16},"end":{"line":5,"column":34}},"3":{"start":{"line":6,"column":12},"end":{"line":6,"column":21}},"4":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"5":{"start":{"line":8,"column":13},"end":{"line":8,"column":28}},"6":{"start":{"line":9,"column":23},"end":{"line":9,"column":62}},"7":{"start":{"line":10,"column":23},"end":{"line":10,"column":62}},"8":{"start":{"line":11,"column":19},"end":{"line":11,"column":54}},"9":{"start":{"line":12,"column":22},"end":{"line":12,"column":60}},"10":{"start":{"line":13,"column":22},"end":{"line":13,"column":49}},"11":{"start":{"line":14,"column":31},"end":{"line":14,"column":63}},"12":{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},"13":{"start":{"line":18,"column":2},"end":{"line":18,"column":75}},"14":{"start":{"line":19,"column":2},"end":{"line":19,"column":18}},"15":{"start":{"line":22,"column":14},"end":{"line":22,"column":64}},"16":{"start":{"line":25,"column":2},"end":{"line":47,"column":3}},"17":{"start":{"line":26,"column":18},"end":{"line":26,"column":73}},"18":{"start":{"line":29,"column":27},"end":{"line":39,"column":5}},"19":{"start":{"line":41,"column":19},"end":{"line":41,"column":62}},"20":{"start":{"line":42,"column":21},"end":{"line":42,"column":42}},"21":{"start":{"line":43,"column":4},"end":{"line":43,"column":27}},"22":{"start":{"line":45,"column":4},"end":{"line":45,"column":48}},"23":{"start":{"line":46,"column":4},"end":{"line":46,"column":16}},"24":{"start":{"line":51,"column":0},"end":{"line":51,"column":16}},"25":{"start":{"line":52,"column":0},"end":{"line":52,"column":24}},"26":{"start":{"line":53,"column":0},"end":{"line":53,"column":48}},"27":{"start":{"line":56,"column":0},"end":{"line":56,"column":35}},"28":{"start":{"line":59,"column":0},"end":{"line":59,"column":56}},"29":{"start":{"line":60,"column":0},"end":{"line":60,"column":60}},"30":{"start":{"line":63,"column":0},"end":{"line":75,"column":3}},"31":{"start":{"line":64,"column":2},"end":{"line":74,"column":3}},"32":{"start":{"line":65,"column":23},"end":{"line":65,"column":31}},"33":{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},"34":{"start":{"line":67,"column":6},"end":{"line":67,"column":68}},"35":{"start":{"line":69,"column":21},"end":{"line":69,"column":48}},"36":{"start":{"line":70,"column":4},"end":{"line":70,"column":39}},"37":{"start":{"line":72,"column":4},"end":{"line":72,"column":47}},"38":{"start":{"line":73,"column":4},"end":{"line":73,"column":63}},"39":{"start":{"line":78,"column":0},"end":{"line":78,"column":47}},"40":{"start":{"line":79,"column":0},"end":{"line":79,"column":41}},"41":{"start":{"line":80,"column":0},"end":{"line":80,"column":54}},"42":{"start":{"line":83,"column":0},"end":{"line":83,"column":24}},"43":{"start":{"line":86,"column":0},"end":{"line":86,"column":46}},"44":{"start":{"line":87,"column":0},"end":{"line":87,"column":50}},"45":{"start":{"line":88,"column":0},"end":{"line":88,"column":52}},"46":{"start":{"line":89,"column":0},"end":{"line":89,"column":66}},"47":{"start":{"line":90,"column":0},"end":{"line":90,"column":47}},"48":{"start":{"line":91,"column":0},"end":{"line":91,"column":73}},"49":{"start":{"line":94,"column":0},"end":{"line":97,"column":3}},"50":{"start":{"line":95,"column":2},"end":{"line":95,"column":38}},"51":{"start":{"line":96,"column":2},"end":{"line":96,"column":61}},"52":{"start":{"line":114,"column":0},"end":{"line":114,"column":21}}},"fnMap":{"0":{"name":"geminiApi","decl":{"start":{"line":24,"column":15},"end":{"line":24,"column":24}},"loc":{"start":{"line":24,"column":37},"end":{"line":48,"column":1}},"line":24},"1":{"name":"(anonymous_1)","decl":{"start":{"line":63,"column":16},"end":{"line":63,"column":17}},"loc":{"start":{"line":63,"column":36},"end":{"line":75,"column":1}},"line":63},"2":{"name":"(anonymous_2)","decl":{"start":{"line":94,"column":8},"end":{"line":94,"column":9}},"loc":{"start":{"line":94,"column":33},"end":{"line":97,"column":1}},"line":94}},"branchMap":{"0":{"loc":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"type":"if","locations":[{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},{"start":{},"end":{}}],"line":1},"1":{"loc":{"start":{"line":7,"column":13},"end":{"line":7,"column":37}},"type":"binary-expr","locations":[{"start":{"line":7,"column":13},"end":{"line":7,"column":29}},{"start":{"line":7,"column":33},"end":{"line":7,"column":37}}],"line":7},"2":{"loc":{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},"type":"if","locations":[{"start":{"line":17,"column":0},"end":{"line":20,"column":1}},{"start":{},"end":{}}],"line":17},"3":{"loc":{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},"type":"if","locations":[{"start":{"line":66,"column":4},"end":{"line":68,"column":5}},{"start":{},"end":{}}],"line":66}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":2,"10":2,"11":2,"12":2,"13":0,"14":0,"15":2,"16":2,"17":2,"18":2,"19":2,"20":0,"21":0,"22":2,"23":2,"24":2,"25":2,"26":2,"27":2,"28":2,"29":2,"30":2,"31":3,"32":3,"33":3,"34":1,"35":2,"36":0,"37":2,"38":2,"39":2,"40":2,"41":2,"42":2,"43":2,"44":2,"45":2,"46":2,"47":2,"48":2,"49":2,"50":0,"51":0,"52":2},"f":{"0":2,"1":3,"2":0},"b":{"0":[2,0],"1":[2,2],"2":[0,2],"3":[1,2]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"7e3d108e96df263bc7369faea4f9b1bb985ed883"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\Controller.js","statementMap":{"0":{"start":{"line":1,"column":41},"end":{"line":1,"column":61}},"1":{"start":{"line":6,"column":4},"end":{"line":31,"column":5}},"2":{"start":{"line":7,"column":22},"end":{"line":7,"column":50}},"3":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"4":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"5":{"start":{"line":14,"column":31},"end":{"line":22,"column":10}},"6":{"start":{"line":15,"column":10},"end":{"line":21,"column":12}},"7":{"start":{"line":24,"column":8},"end":{"line":24,"column":52}},"8":{"start":{"line":28,"column":6},"end":{"line":28,"column":36}},"9":{"start":{"line":30,"column":6},"end":{"line":30,"column":65}},"10":{"start":{"line":36,"column":4},"end":{"line":65,"column":5}},"11":{"start":{"line":37,"column":21},"end":{"line":37,"column":31}},"12":{"start":{"line":39,"column":21},"end":{"line":39,"column":52}},"13":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"14":{"start":{"line":42,"column":8},"end":{"line":42,"column":75}},"15":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"16":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"17":{"start":{"line":50,"column":30},"end":{"line":56,"column":9}},"18":{"start":{"line":58,"column":8},"end":{"line":58,"column":51}},"19":{"start":{"line":62,"column":6},"end":{"line":62,"column":35}},"20":{"start":{"line":64,"column":6},"end":{"line":64,"column":65}},"21":{"start":{"line":70,"column":4},"end":{"line":100,"column":5}},"22":{"start":{"line":71,"column":21},"end":{"line":71,"column":32}},"23":{"start":{"line":72,"column":33},"end":{"line":72,"column":43}},"24":{"start":{"line":75,"column":21},"end":{"line":75,"column":64}},"25":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"26":{"start":{"line":78,"column":8},"end":{"line":78,"column":75}},"27":{"start":{"line":82,"column":31},"end":{"line":84,"column":8}},"28":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"29":{"start":{"line":87,"column":8},"end":{"line":89,"column":72}},"30":{"start":{"line":93,"column":6},"end":{"line":93,"column":56}},"31":{"start":{"line":95,"column":6},"end":{"line":97,"column":69}},"32":{"start":{"line":99,"column":6},"end":{"line":99,"column":65}},"33":{"start":{"line":105,"column":4},"end":{"line":119,"column":5}},"34":{"start":{"line":106,"column":21},"end":{"line":106,"column":32}},"35":{"start":{"line":108,"column":24},"end":{"line":111,"column":8}},"36":{"start":{"line":113,"column":30},"end":{"line":113,"column":70}},"37":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":44}},"39":{"start":{"line":117,"column":6},"end":{"line":117,"column":27}},"40":{"start":{"line":118,"column":6},"end":{"line":118,"column":65}},"41":{"start":{"line":124,"column":4},"end":{"line":145,"column":5}},"42":{"start":{"line":125,"column":21},"end":{"line":125,"column":32}},"43":{"start":{"line":126,"column":33},"end":{"line":126,"column":43}},"44":{"start":{"line":129,"column":23},"end":{"line":131,"column":8}},"45":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"46":{"start":{"line":134,"column":8},"end":{"line":136,"column":72}},"47":{"start":{"line":140,"column":6},"end":{"line":140,"column":31}},"48":{"start":{"line":142,"column":6},"end":{"line":142,"column":80}},"49":{"start":{"line":144,"column":6},"end":{"line":144,"column":65}},"50":{"start":{"line":149,"column":0},"end":{"line":149,"column":28}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":2},"end":{"line":5,"column":3}},"loc":{"start":{"line":5,"column":39},"end":{"line":32,"column":3}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":14,"column":43},"end":{"line":14,"column":44}},"loc":{"start":{"line":14,"column":55},"end":{"line":22,"column":9}},"line":14},"2":{"name":"(anonymous_2)","decl":{"start":{"line":35,"column":2},"end":{"line":35,"column":3}},"loc":{"start":{"line":35,"column":39},"end":{"line":66,"column":3}},"line":35},"3":{"name":"(anonymous_3)","decl":{"start":{"line":69,"column":2},"end":{"line":69,"column":3}},"loc":{"start":{"line":69,"column":40},"end":{"line":101,"column":3}},"line":69},"4":{"name":"(anonymous_4)","decl":{"start":{"line":104,"column":2},"end":{"line":104,"column":3}},"loc":{"start":{"line":104,"column":38},"end":{"line":120,"column":3}},"line":104},"5":{"name":"(anonymous_5)","decl":{"start":{"line":113,"column":44},"end":{"line":113,"column":45}},"loc":{"start":{"line":113,"column":53},"end":{"line":113,"column":69}},"line":113},"6":{"name":"(anonymous_6)","decl":{"start":{"line":123,"column":2},"end":{"line":123,"column":3}},"loc":{"start":{"line":123,"column":45},"end":{"line":146,"column":3}},"line":123}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":25},"end":{"line":10,"column":48}},"type":"cond-expr","locations":[{"start":{"line":10,"column":36},"end":{"line":10,"column":40}},{"start":{"line":10,"column":43},"end":{"line":10,"column":48}}],"line":10},"1":{"loc":{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},"type":"if","locations":[{"start":{"line":13,"column":6},"end":{"line":25,"column":7}},{"start":{},"end":{}}],"line":13},"2":{"loc":{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":41,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":41},"3":{"loc":{"start":{"line":46,"column":25},"end":{"line":46,"column":48}},"type":"cond-expr","locations":[{"start":{"line":46,"column":36},"end":{"line":46,"column":40}},{"start":{"line":46,"column":43},"end":{"line":46,"column":48}}],"line":46},"4":{"loc":{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},"type":"if","locations":[{"start":{"line":49,"column":6},"end":{"line":59,"column":7}},{"start":{},"end":{}}],"line":49},"5":{"loc":{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},"type":"if","locations":[{"start":{"line":77,"column":6},"end":{"line":79,"column":7}},{"start":{},"end":{}}],"line":77},"6":{"loc":{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},"type":"if","locations":[{"start":{"line":86,"column":6},"end":{"line":90,"column":7}},{"start":{},"end":{}}],"line":86},"7":{"loc":{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":137,"column":7}},{"start":{},"end":{}}],"line":133}},"s":{"0":2,"1":3,"2":3,"3":2,"4":2,"5":1,"6":1,"7":1,"8":1,"9":1,"10":4,"11":4,"12":4,"13":3,"14":1,"15":2,"16":2,"17":1,"18":1,"19":1,"20":1,"21":4,"22":4,"23":4,"24":4,"25":3,"26":1,"27":2,"28":2,"29":1,"30":1,"31":1,"32":1,"33":2,"34":2,"35":2,"36":1,"37":1,"38":1,"39":1,"40":1,"41":3,"42":3,"43":3,"44":3,"45":2,"46":1,"47":1,"48":1,"49":1,"50":2},"f":{"0":3,"1":1,"2":4,"3":4,"4":2,"5":1,"6":3},"b":{"0":[1,1],"1":[1,1],"2":[1,2],"3":[1,1],"4":[1,1],"5":[1,2],"6":[1,1],"7":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"737b92e119bcfa4ab8431d4a132d7beeb4e23ad7"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\PubController.js","statementMap":{"0":{"start":{"line":4,"column":4},"end":{"line":4,"column":24}},"1":{"start":{"line":8,"column":4},"end":{"line":60,"column":5}},"2":{"start":{"line":9,"column":51},"end":{"line":9,"column":60}},"3":{"start":{"line":12,"column":25},"end":{"line":12,"column":27}},"4":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"5":{"start":{"line":16,"column":8},"end":{"line":21,"column":10}},"6":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"7":{"start":{"line":26,"column":8},"end":{"line":29,"column":10}},"8":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"9":{"start":{"line":30,"column":22},"end":{"line":30,"column":66}},"10":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"11":{"start":{"line":31,"column":22},"end":{"line":31,"column":66}},"12":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"13":{"start":{"line":36,"column":31},"end":{"line":36,"column":46}},"14":{"start":{"line":37,"column":28},"end":{"line":37,"column":52}},"15":{"start":{"line":38,"column":28},"end":{"line":38,"column":43}},"16":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"17":{"start":{"line":41,"column":10},"end":{"line":41,"column":62}},"18":{"start":{"line":45,"column":22},"end":{"line":45,"column":62}},"19":{"start":{"line":48,"column":29},"end":{"line":54,"column":9}},"20":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"21":{"start":{"line":56,"column":6},"end":{"line":56,"column":43}},"22":{"start":{"line":58,"column":6},"end":{"line":58,"column":54}},"23":{"start":{"line":59,"column":6},"end":{"line":59,"column":65}},"24":{"start":{"line":65,"column":4},"end":{"line":78,"column":5}},"25":{"start":{"line":66,"column":21},"end":{"line":66,"column":31}},"26":{"start":{"line":68,"column":21},"end":{"line":68,"column":52}},"27":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"28":{"start":{"line":71,"column":8},"end":{"line":71,"column":75}},"29":{"start":{"line":75,"column":6},"end":{"line":75,"column":35}},"30":{"start":{"line":77,"column":6},"end":{"line":77,"column":65}},"31":{"start":{"line":82,"column":0},"end":{"line":82,"column":31}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":7,"column":2},"end":{"line":7,"column":3}},"loc":{"start":{"line":7,"column":39},"end":{"line":61,"column":3}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":41},"end":{"line":48,"column":42}},"loc":{"start":{"line":48,"column":54},"end":{"line":54,"column":7}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":64,"column":2},"end":{"line":64,"column":3}},"loc":{"start":{"line":64,"column":39},"end":{"line":79,"column":3}},"line":64}},"branchMap":{"0":{"loc":{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},"type":"if","locations":[{"start":{"line":15,"column":6},"end":{"line":22,"column":7}},{"start":{},"end":{}}],"line":15},"1":{"loc":{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},"type":"if","locations":[{"start":{"line":25,"column":6},"end":{"line":32,"column":7}},{"start":{},"end":{}}],"line":25},"2":{"loc":{"start":{"line":25,"column":10},"end":{"line":25,"column":30}},"type":"binary-expr","locations":[{"start":{"line":25,"column":10},"end":{"line":25,"column":18}},{"start":{"line":25,"column":22},"end":{"line":25,"column":30}}],"line":25},"3":{"loc":{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},"type":"if","locations":[{"start":{"line":30,"column":8},"end":{"line":30,"column":66}},{"start":{},"end":{}}],"line":30},"4":{"loc":{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},"type":"if","locations":[{"start":{"line":31,"column":8},"end":{"line":31,"column":66}},{"start":{},"end":{}}],"line":31},"5":{"loc":{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},"type":"if","locations":[{"start":{"line":35,"column":6},"end":{"line":43,"column":7}},{"start":{},"end":{}}],"line":35},"6":{"loc":{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},"type":"if","locations":[{"start":{"line":40,"column":8},"end":{"line":42,"column":9}},{"start":{},"end":{}}],"line":40},"7":{"loc":{"start":{"line":40,"column":12},"end":{"line":40,"column":70}},"type":"binary-expr","locations":[{"start":{"line":40,"column":12},"end":{"line":40,"column":39}},{"start":{"line":40,"column":43},"end":{"line":40,"column":70}}],"line":40},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},"type":"if","locations":[{"start":{"line":70,"column":6},"end":{"line":72,"column":7}},{"start":{},"end":{}}],"line":70}},"s":{"0":2,"1":7,"2":7,"3":7,"4":7,"5":1,"6":7,"7":3,"8":3,"9":2,"10":3,"11":2,"12":7,"13":2,"14":2,"15":2,"16":2,"17":1,"18":7,"19":6,"20":6,"21":6,"22":1,"23":1,"24":3,"25":3,"26":3,"27":2,"28":1,"29":1,"30":1,"31":2},"f":{"0":7,"1":6,"2":3},"b":{"0":[1,6],"1":[3,4],"2":[7,5],"3":[2,1],"4":[2,1],"5":[2,5],"6":[1,1],"7":[2,1],"8":[1,1]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"50d00d019a8a66eca334a967de982b42c35cca4d"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\controllers\\UserController.js","statementMap":{"0":{"start":{"line":1,"column":17},"end":{"line":1,"column":37}},"1":{"start":{"line":2,"column":28},"end":{"line":2,"column":56}},"2":{"start":{"line":3,"column":24},"end":{"line":3,"column":49}},"3":{"start":{"line":4,"column":25},"end":{"line":4,"column":55}},"4":{"start":{"line":5,"column":15},"end":{"line":5,"column":33}},"5":{"start":{"line":9,"column":4},"end":{"line":45,"column":5}},"6":{"start":{"line":10,"column":30},"end":{"line":10,"column":38}},"7":{"start":{"line":11,"column":21},"end":{"line":14,"column":8}},"8":{"start":{"line":15,"column":22},"end":{"line":15,"column":41}},"9":{"start":{"line":17,"column":17},"end":{"line":19,"column":8}},"10":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"11":{"start":{"line":22,"column":8},"end":{"line":26,"column":11}},"12":{"start":{"line":29,"column":27},"end":{"line":33,"column":7}},"13":{"start":{"line":35,"column":20},"end":{"line":35,"column":45}},"14":{"start":{"line":36,"column":6},"end":{"line":41,"column":9}},"15":{"start":{"line":43,"column":6},"end":{"line":43,"column":25}},"16":{"start":{"line":44,"column":6},"end":{"line":44,"column":65}},"17":{"start":{"line":49,"column":4},"end":{"line":72,"column":5}},"18":{"start":{"line":50,"column":44},"end":{"line":50,"column":52}},"19":{"start":{"line":52,"column":22},"end":{"line":56,"column":8}},"20":{"start":{"line":58,"column":6},"end":{"line":62,"column":9}},"21":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"22":{"start":{"line":68,"column":8},"end":{"line":68,"column":67}},"23":{"start":{"line":70,"column":8},"end":{"line":70,"column":67}},"24":{"start":{"line":76,"column":4},"end":{"line":116,"column":5}},"25":{"start":{"line":77,"column":34},"end":{"line":77,"column":42}},"26":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"27":{"start":{"line":80,"column":8},"end":{"line":82,"column":64}},"28":{"start":{"line":85,"column":19},"end":{"line":87,"column":8}},"29":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"30":{"start":{"line":90,"column":8},"end":{"line":90,"column":67}},"31":{"start":{"line":93,"column":30},"end":{"line":93,"column":70}},"32":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"33":{"start":{"line":96,"column":8},"end":{"line":96,"column":69}},"34":{"start":{"line":99,"column":22},"end":{"line":103,"column":7}},"35":{"start":{"line":105,"column":20},"end":{"line":105,"column":40}},"36":{"start":{"line":107,"column":6},"end":{"line":112,"column":9}},"37":{"start":{"line":114,"column":6},"end":{"line":114,"column":27}},"38":{"start":{"line":115,"column":6},"end":{"line":115,"column":65}},"39":{"start":{"line":120,"column":4},"end":{"line":161,"column":5}},"40":{"start":{"line":121,"column":21},"end":{"line":121,"column":32}},"41":{"start":{"line":122,"column":34},"end":{"line":122,"column":42}},"42":{"start":{"line":124,"column":19},"end":{"line":124,"column":46}},"43":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"44":{"start":{"line":127,"column":8},"end":{"line":127,"column":73}},"45":{"start":{"line":130,"column":27},"end":{"line":130,"column":29}},"46":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"47":{"start":{"line":132,"column":20},"end":{"line":132,"column":53}},"48":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"49":{"start":{"line":133,"column":17},"end":{"line":133,"column":44}},"50":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"51":{"start":{"line":136,"column":8},"end":{"line":138,"column":61}},"52":{"start":{"line":141,"column":6},"end":{"line":141,"column":38}},"53":{"start":{"line":143,"column":6},"end":{"line":150,"column":9}},"54":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"55":{"start":{"line":156,"column":8},"end":{"line":156,"column":67}},"56":{"start":{"line":158,"column":8},"end":{"line":158,"column":29}},"57":{"start":{"line":159,"column":8},"end":{"line":159,"column":67}},"58":{"start":{"line":165,"column":0},"end":{"line":165,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":8,"column":2},"end":{"line":8,"column":3}},"loc":{"start":{"line":8,"column":37},"end":{"line":46,"column":3}},"line":8},"1":{"name":"(anonymous_1)","decl":{"start":{"line":48,"column":2},"end":{"line":48,"column":3}},"loc":{"start":{"line":48,"column":34},"end":{"line":73,"column":3}},"line":48},"2":{"name":"(anonymous_2)","decl":{"start":{"line":75,"column":2},"end":{"line":75,"column":3}},"loc":{"start":{"line":75,"column":31},"end":{"line":117,"column":3}},"line":75},"3":{"name":"(anonymous_3)","decl":{"start":{"line":119,"column":2},"end":{"line":119,"column":3}},"loc":{"start":{"line":119,"column":36},"end":{"line":162,"column":3}},"line":119}},"branchMap":{"0":{"loc":{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":27,"column":7}},{"start":{},"end":{}}],"line":21},"1":{"loc":{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},"type":"if","locations":[{"start":{"line":64,"column":6},"end":{"line":71,"column":7}},{"start":{"line":69,"column":13},"end":{"line":71,"column":7}}],"line":64},"2":{"loc":{"start":{"line":65,"column":8},"end":{"line":66,"column":55}},"type":"binary-expr","locations":[{"start":{"line":65,"column":8},"end":{"line":65,"column":49}},{"start":{"line":66,"column":8},"end":{"line":66,"column":55}}],"line":65},"3":{"loc":{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},"type":"if","locations":[{"start":{"line":79,"column":6},"end":{"line":83,"column":7}},{"start":{},"end":{}}],"line":79},"4":{"loc":{"start":{"line":79,"column":10},"end":{"line":79,"column":29}},"type":"binary-expr","locations":[{"start":{"line":79,"column":10},"end":{"line":79,"column":16}},{"start":{"line":79,"column":20},"end":{"line":79,"column":29}}],"line":79},"5":{"loc":{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},"type":"if","locations":[{"start":{"line":89,"column":6},"end":{"line":91,"column":7}},{"start":{},"end":{}}],"line":89},"6":{"loc":{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},"type":"if","locations":[{"start":{"line":95,"column":6},"end":{"line":97,"column":7}},{"start":{},"end":{}}],"line":95},"7":{"loc":{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},"type":"if","locations":[{"start":{"line":126,"column":6},"end":{"line":128,"column":7}},{"start":{},"end":{}}],"line":126},"8":{"loc":{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},"type":"if","locations":[{"start":{"line":132,"column":6},"end":{"line":132,"column":53}},{"start":{},"end":{}}],"line":132},"9":{"loc":{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},"type":"if","locations":[{"start":{"line":133,"column":6},"end":{"line":133,"column":44}},{"start":{},"end":{}}],"line":133},"10":{"loc":{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},"type":"if","locations":[{"start":{"line":135,"column":6},"end":{"line":139,"column":7}},{"start":{},"end":{}}],"line":135},"11":{"loc":{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},"type":"if","locations":[{"start":{"line":152,"column":6},"end":{"line":160,"column":7}},{"start":{"line":157,"column":13},"end":{"line":160,"column":7}}],"line":152},"12":{"loc":{"start":{"line":153,"column":8},"end":{"line":154,"column":55}},"type":"binary-expr","locations":[{"start":{"line":153,"column":8},"end":{"line":153,"column":49}},{"start":{"line":154,"column":8},"end":{"line":154,"column":55}}],"line":153}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":1,"6":1,"7":1,"8":0,"9":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":1,"16":1,"17":4,"18":4,"19":4,"20":1,"21":3,"22":2,"23":1,"24":5,"25":5,"26":5,"27":1,"28":4,"29":3,"30":1,"31":2,"32":2,"33":2,"34":0,"35":0,"36":0,"37":1,"38":1,"39":4,"40":4,"41":4,"42":4,"43":4,"44":0,"45":4,"46":4,"47":1,"48":4,"49":2,"50":4,"51":2,"52":2,"53":1,"54":1,"55":1,"56":0,"57":0,"58":2},"f":{"0":1,"1":4,"2":5,"3":4},"b":{"0":[0,0],"1":[2,1],"2":[3,2],"3":[1,4],"4":[5,5],"5":[1,2],"6":[2,0],"7":[0,4],"8":[1,3],"9":[2,2],"10":[2,2],"11":[1,0],"12":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"1cc80a39bdb2f0e086371f69dc5963f6c4e5b9ee"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\bcrypt.js","statementMap":{"0":{"start":{"line":1,"column":15},"end":{"line":1,"column":34}},"1":{"start":{"line":3,"column":21},"end":{"line":5,"column":1}},"2":{"start":{"line":4,"column":2},"end":{"line":4,"column":39}},"3":{"start":{"line":7,"column":24},"end":{"line":9,"column":1}},"4":{"start":{"line":8,"column":2},"end":{"line":8,"column":54}},"5":{"start":{"line":11,"column":0},"end":{"line":14,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":21},"end":{"line":3,"column":22}},"loc":{"start":{"line":3,"column":35},"end":{"line":5,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":24},"end":{"line":7,"column":25}},"loc":{"start":{"line":7,"column":54},"end":{"line":9,"column":1}},"line":7}},"branchMap":{},"s":{"0":2,"1":2,"2":4,"3":2,"4":2,"5":2},"f":{"0":4,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"c74a0375d91f19b5bcb02453f3e0a1ea35f7a31a"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\helpers\\jwt.js","statementMap":{"0":{"start":{"line":1,"column":12},"end":{"line":1,"column":35}},"1":{"start":{"line":2,"column":19},"end":{"line":2,"column":41}},"2":{"start":{"line":4,"column":20},"end":{"line":6,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":5,"column":39}},"4":{"start":{"line":8,"column":20},"end":{"line":10,"column":1}},"5":{"start":{"line":9,"column":2},"end":{"line":9,"column":39}},"6":{"start":{"line":12,"column":0},"end":{"line":15,"column":2}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":20},"end":{"line":4,"column":21}},"loc":{"start":{"line":4,"column":33},"end":{"line":6,"column":1}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":8,"column":20},"end":{"line":8,"column":21}},"loc":{"start":{"line":8,"column":31},"end":{"line":10,"column":1}},"line":8}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":23,"6":2},"f":{"0":2,"1":23},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"70740eafddc547dd656de5caf7cef5487e23bfce"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\middlewares\\authentication.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":1,"column":49}},"1":{"start":{"line":2,"column":17},"end":{"line":2,"column":37}},"2":{"start":{"line":4,"column":23},"end":{"line":31,"column":1}},"3":{"start":{"line":5,"column":2},"end":{"line":30,"column":3}},"4":{"start":{"line":6,"column":29},"end":{"line":6,"column":40}},"5":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"6":{"start":{"line":9,"column":6},"end":{"line":9,"column":20}},"7":{"start":{"line":12,"column":20},"end":{"line":12,"column":45}},"8":{"start":{"line":14,"column":17},"end":{"line":14,"column":48}},"9":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"10":{"start":{"line":17,"column":6},"end":{"line":17,"column":65}},"11":{"start":{"line":20,"column":4},"end":{"line":24,"column":6}},"12":{"start":{"line":26,"column":4},"end":{"line":26,"column":11}},"13":{"start":{"line":28,"column":4},"end":{"line":28,"column":25}},"14":{"start":{"line":29,"column":4},"end":{"line":29,"column":62}},"15":{"start":{"line":33,"column":0},"end":{"line":33,"column":32}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":4,"column":23},"end":{"line":4,"column":24}},"loc":{"start":{"line":4,"column":49},"end":{"line":31,"column":1}},"line":4}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},"type":"if","locations":[{"start":{"line":8,"column":4},"end":{"line":10,"column":5}},{"start":{},"end":{}}],"line":8},"1":{"loc":{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},"type":"if","locations":[{"start":{"line":16,"column":4},"end":{"line":18,"column":5}},{"start":{},"end":{}}],"line":16}},"s":{"0":2,"1":2,"2":2,"3":25,"4":25,"5":25,"6":2,"7":23,"8":22,"9":20,"10":2,"11":18,"12":18,"13":3,"14":3,"15":2},"f":{"0":25},"b":{"0":[2,23],"1":[2,18]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"5d1e4c9847324fb3bbc0718d5578aad6ca86b28f"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\favorite.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":49,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":12,"column":64}},"3":{"start":{"line":13,"column":6},"end":{"line":13,"column":80}},"4":{"start":{"line":16,"column":2},"end":{"line":47,"column":4}},"5":{"start":{"line":48,"column":2},"end":{"line":48,"column":18}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":49,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":14,"column":5}},"line":10}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2},"f":{"0":2,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"a46f0a38c4e9fb02a98da2a78e922bcd91492325"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\index.js","statementMap":{"0":{"start":{"line":3,"column":11},"end":{"line":3,"column":24}},"1":{"start":{"line":4,"column":13},"end":{"line":4,"column":28}},"2":{"start":{"line":5,"column":18},"end":{"line":5,"column":38}},"3":{"start":{"line":6,"column":16},"end":{"line":6,"column":34}},"4":{"start":{"line":7,"column":17},"end":{"line":7,"column":42}},"5":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"6":{"start":{"line":9,"column":15},"end":{"line":9,"column":65}},"7":{"start":{"line":10,"column":11},"end":{"line":10,"column":13}},"8":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":74}},"10":{"start":{"line":16,"column":2},"end":{"line":16,"column":87}},"11":{"start":{"line":19,"column":0},"end":{"line":32,"column":5}},"12":{"start":{"line":22,"column":4},"end":{"line":27,"column":6}},"13":{"start":{"line":30,"column":18},"end":{"line":30,"column":85}},"14":{"start":{"line":31,"column":4},"end":{"line":31,"column":27}},"15":{"start":{"line":34,"column":0},"end":{"line":38,"column":3}},"16":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":32}},"18":{"start":{"line":40,"column":0},"end":{"line":40,"column":25}},"19":{"start":{"line":41,"column":0},"end":{"line":41,"column":25}},"20":{"start":{"line":43,"column":0},"end":{"line":43,"column":20}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":10},"end":{"line":21,"column":11}},"loc":{"start":{"line":21,"column":18},"end":{"line":28,"column":3}},"line":21},"1":{"name":"(anonymous_1)","decl":{"start":{"line":29,"column":11},"end":{"line":29,"column":12}},"loc":{"start":{"line":29,"column":19},"end":{"line":32,"column":3}},"line":29},"2":{"name":"(anonymous_2)","decl":{"start":{"line":34,"column":24},"end":{"line":34,"column":25}},"loc":{"start":{"line":34,"column":37},"end":{"line":38,"column":1}},"line":34}},"branchMap":{"0":{"loc":{"start":{"line":8,"column":12},"end":{"line":8,"column":49}},"type":"binary-expr","locations":[{"start":{"line":8,"column":12},"end":{"line":8,"column":32}},{"start":{"line":8,"column":36},"end":{"line":8,"column":49}}],"line":8},"1":{"loc":{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},"type":"if","locations":[{"start":{"line":13,"column":0},"end":{"line":17,"column":1}},{"start":{"line":15,"column":7},"end":{"line":17,"column":1}}],"line":13},"2":{"loc":{"start":{"line":23,"column":6},"end":{"line":26,"column":37}},"type":"binary-expr","locations":[{"start":{"line":23,"column":6},"end":{"line":23,"column":29}},{"start":{"line":24,"column":6},"end":{"line":24,"column":23}},{"start":{"line":25,"column":6},"end":{"line":25,"column":30}},{"start":{"line":26,"column":6},"end":{"line":26,"column":37}}],"line":23},"3":{"loc":{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":37,"column":3}},{"start":{},"end":{}}],"line":35}},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":2,"6":2,"7":2,"8":2,"9":0,"10":2,"11":2,"12":8,"13":6,"14":6,"15":2,"16":6,"17":6,"18":2,"19":2,"20":2},"f":{"0":8,"1":6,"2":6},"b":{"0":[2,0],"1":[0,2],"2":[8,8,6,6],"3":[6,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e8d39eb6f77ca39eed5252a0ef5942b3889b988e"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\user.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":25},"end":{"line":3,"column":53}},"2":{"start":{"line":5,"column":0},"end":{"line":71,"column":2}},"3":{"start":{"line":14,"column":6},"end":{"line":17,"column":9}},"4":{"start":{"line":20,"column":2},"end":{"line":69,"column":4}},"5":{"start":{"line":63,"column":10},"end":{"line":63,"column":54}},"6":{"start":{"line":70,"column":2},"end":{"line":70,"column":14}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":17},"end":{"line":5,"column":18}},"loc":{"start":{"line":5,"column":43},"end":{"line":71,"column":1}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":4},"end":{"line":12,"column":5}},"loc":{"start":{"line":12,"column":29},"end":{"line":18,"column":5}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":62,"column":22},"end":{"line":62,"column":23}},"loc":{"start":{"line":62,"column":32},"end":{"line":64,"column":9}},"line":62}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2,"5":3,"6":2},"f":{"0":2,"1":2,"2":3},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d6cc21ed517c0c2dd5d8da5464a276b765e1e5bd"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\models\\xiaomidevice.js","statementMap":{"0":{"start":{"line":2,"column":18},"end":{"line":2,"column":38}},"1":{"start":{"line":3,"column":0},"end":{"line":43,"column":2}},"2":{"start":{"line":12,"column":6},"end":{"line":15,"column":9}},"3":{"start":{"line":18,"column":2},"end":{"line":41,"column":4}},"4":{"start":{"line":42,"column":2},"end":{"line":42,"column":22}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":17},"end":{"line":3,"column":18}},"loc":{"start":{"line":3,"column":43},"end":{"line":43,"column":1}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":4},"end":{"line":10,"column":5}},"loc":{"start":{"line":10,"column":29},"end":{"line":16,"column":5}},"line":10}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":2,"4":2},"f":{"0":2,"1":2},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"3d210710bceff5c6be55260373affab9d6228f0d"}
+,"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js": {"path":"D:\\1_Hacktiv8\\Phase2\\Week3\\W3D1,Senin-28April2025\\Tugas\\IP-RMT60\\server\\routes\\testing.js","statementMap":{"0":{"start":{"line":1,"column":16},"end":{"line":1,"column":34}},"1":{"start":{"line":2,"column":15},"end":{"line":2,"column":31}},"2":{"start":{"line":5,"column":0},"end":{"line":7,"column":3}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":54}},"4":{"start":{"line":9,"column":0},"end":{"line":9,"column":24}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":5,"column":22},"end":{"line":5,"column":23}},"loc":{"start":{"line":5,"column":36},"end":{"line":7,"column":1}},"line":5}},"branchMap":{},"s":{"0":2,"1":2,"2":2,"3":1,"4":2},"f":{"0":1},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"97208ba7b910d8e53a4dc377db1b8047b51b5c08"}
+}
diff --git a/server/coverage/lcov-report/base.css b/server/coverage/lcov-report/base.css
new file mode 100644
index 00000000..f418035b
--- /dev/null
+++ b/server/coverage/lcov-report/base.css
@@ -0,0 +1,224 @@
+body, html {
+ margin:0; padding: 0;
+ height: 100%;
+}
+body {
+ font-family: Helvetica Neue, Helvetica, Arial;
+ font-size: 14px;
+ color:#333;
+}
+.small { font-size: 12px; }
+*, *:after, *:before {
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing:border-box;
+ box-sizing:border-box;
+ }
+h1 { font-size: 20px; margin: 0;}
+h2 { font-size: 14px; }
+pre {
+ font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ margin: 0;
+ padding: 0;
+ -moz-tab-size: 2;
+ -o-tab-size: 2;
+ tab-size: 2;
+}
+a { color:#0074D9; text-decoration:none; }
+a:hover { text-decoration:underline; }
+.strong { font-weight: bold; }
+.space-top1 { padding: 10px 0 0 0; }
+.pad2y { padding: 20px 0; }
+.pad1y { padding: 10px 0; }
+.pad2x { padding: 0 20px; }
+.pad2 { padding: 20px; }
+.pad1 { padding: 10px; }
+.space-left2 { padding-left:55px; }
+.space-right2 { padding-right:20px; }
+.center { text-align:center; }
+.clearfix { display:block; }
+.clearfix:after {
+ content:'';
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+ }
+.fl { float: left; }
+@media only screen and (max-width:640px) {
+ .col3 { width:100%; max-width:100%; }
+ .hide-mobile { display:none!important; }
+}
+
+.quiet {
+ color: #7f7f7f;
+ color: rgba(0,0,0,0.5);
+}
+.quiet a { opacity: 0.7; }
+
+.fraction {
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+ font-size: 10px;
+ color: #555;
+ background: #E8E8E8;
+ padding: 4px 5px;
+ border-radius: 3px;
+ vertical-align: middle;
+}
+
+div.path a:link, div.path a:visited { color: #333; }
+table.coverage {
+ border-collapse: collapse;
+ margin: 10px 0 0 0;
+ padding: 0;
+}
+
+table.coverage td {
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+}
+table.coverage td.line-count {
+ text-align: right;
+ padding: 0 5px 0 20px;
+}
+table.coverage td.line-coverage {
+ text-align: right;
+ padding-right: 10px;
+ min-width:20px;
+}
+
+table.coverage td span.cline-any {
+ display: inline-block;
+ padding: 0 5px;
+ width: 100%;
+}
+.missing-if-branch {
+ display: inline-block;
+ margin-right: 5px;
+ border-radius: 3px;
+ position: relative;
+ padding: 0 4px;
+ background: #333;
+ color: yellow;
+}
+
+.skip-if-branch {
+ display: none;
+ margin-right: 10px;
+ position: relative;
+ padding: 0 4px;
+ background: #ccc;
+ color: white;
+}
+.missing-if-branch .typ, .skip-if-branch .typ {
+ color: inherit !important;
+}
+.coverage-summary {
+ border-collapse: collapse;
+ width: 100%;
+}
+.coverage-summary tr { border-bottom: 1px solid #bbb; }
+.keyline-all { border: 1px solid #ddd; }
+.coverage-summary td, .coverage-summary th { padding: 10px; }
+.coverage-summary tbody { border: 1px solid #bbb; }
+.coverage-summary td { border-right: 1px solid #bbb; }
+.coverage-summary td:last-child { border-right: none; }
+.coverage-summary th {
+ text-align: left;
+ font-weight: normal;
+ white-space: nowrap;
+}
+.coverage-summary th.file { border-right: none !important; }
+.coverage-summary th.pct { }
+.coverage-summary th.pic,
+.coverage-summary th.abs,
+.coverage-summary td.pct,
+.coverage-summary td.abs { text-align: right; }
+.coverage-summary td.file { white-space: nowrap; }
+.coverage-summary td.pic { min-width: 120px !important; }
+.coverage-summary tfoot td { }
+
+.coverage-summary .sorter {
+ height: 10px;
+ width: 7px;
+ display: inline-block;
+ margin-left: 0.5em;
+ background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
+}
+.coverage-summary .sorted .sorter {
+ background-position: 0 -20px;
+}
+.coverage-summary .sorted-desc .sorter {
+ background-position: 0 -10px;
+}
+.status-line { height: 10px; }
+/* yellow */
+.cbranch-no { background: yellow !important; color: #111; }
+/* dark red */
+.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
+.low .chart { border:1px solid #C21F39 }
+.highlighted,
+.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
+ background: #C21F39 !important;
+}
+/* medium red */
+.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
+/* light red */
+.low, .cline-no { background:#FCE1E5 }
+/* light green */
+.high, .cline-yes { background:rgb(230,245,208) }
+/* medium green */
+.cstat-yes { background:rgb(161,215,106) }
+/* dark green */
+.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
+.high .chart { border:1px solid rgb(77,146,33) }
+/* dark yellow (gold) */
+.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
+.medium .chart { border:1px solid #f9cd0b; }
+/* light yellow */
+.medium { background: #fff4c2; }
+
+.cstat-skip { background: #ddd; color: #111; }
+.fstat-skip { background: #ddd; color: #111 !important; }
+.cbranch-skip { background: #ddd !important; color: #111; }
+
+span.cline-neutral { background: #eaeaea; }
+
+.coverage-summary td.empty {
+ opacity: .5;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ line-height: 1;
+ color: #888;
+}
+
+.cover-fill, .cover-empty {
+ display:inline-block;
+ height: 12px;
+}
+.chart {
+ line-height: 0;
+}
+.cover-empty {
+ background: white;
+}
+.cover-full {
+ border-right: none !important;
+}
+pre.prettyprint {
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+.com { color: #999 !important; }
+.ignore-none { color: #999; font-weight: normal; }
+
+.wrapper {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -48px;
+}
+.footer, .push {
+ height: 48px;
+}
diff --git a/server/coverage/lcov-report/block-navigation.js b/server/coverage/lcov-report/block-navigation.js
new file mode 100644
index 00000000..cc121302
--- /dev/null
+++ b/server/coverage/lcov-report/block-navigation.js
@@ -0,0 +1,87 @@
+/* eslint-disable */
+var jumpToCode = (function init() {
+ // Classes of code we would like to highlight in the file view
+ var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
+
+ // Elements to highlight in the file listing view
+ var fileListingElements = ['td.pct.low'];
+
+ // We don't want to select elements that are direct descendants of another match
+ var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
+
+ // Selecter that finds elements on the page to which we can jump
+ var selector =
+ fileListingElements.join(', ') +
+ ', ' +
+ notSelector +
+ missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
+
+ // The NodeList of matching elements
+ var missingCoverageElements = document.querySelectorAll(selector);
+
+ var currentIndex;
+
+ function toggleClass(index) {
+ missingCoverageElements
+ .item(currentIndex)
+ .classList.remove('highlighted');
+ missingCoverageElements.item(index).classList.add('highlighted');
+ }
+
+ function makeCurrent(index) {
+ toggleClass(index);
+ currentIndex = index;
+ missingCoverageElements.item(index).scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'center'
+ });
+ }
+
+ function goToPrevious() {
+ var nextIndex = 0;
+ if (typeof currentIndex !== 'number' || currentIndex === 0) {
+ nextIndex = missingCoverageElements.length - 1;
+ } else if (missingCoverageElements.length > 1) {
+ nextIndex = currentIndex - 1;
+ }
+
+ makeCurrent(nextIndex);
+ }
+
+ function goToNext() {
+ var nextIndex = 0;
+
+ if (
+ typeof currentIndex === 'number' &&
+ currentIndex < missingCoverageElements.length - 1
+ ) {
+ nextIndex = currentIndex + 1;
+ }
+
+ makeCurrent(nextIndex);
+ }
+
+ return function jump(event) {
+ if (
+ document.getElementById('fileSearch') === document.activeElement &&
+ document.activeElement != null
+ ) {
+ // if we're currently focused on the search input, we don't want to navigate
+ return;
+ }
+
+ switch (event.which) {
+ case 78: // n
+ case 74: // j
+ goToNext();
+ break;
+ case 66: // b
+ case 75: // k
+ case 80: // p
+ goToPrevious();
+ break;
+ }
+ };
+})();
+window.addEventListener('keydown', jumpToCode);
diff --git a/server/coverage/lcov-report/favicon.png b/server/coverage/lcov-report/favicon.png
new file mode 100644
index 00000000..c1525b81
Binary files /dev/null and b/server/coverage/lcov-report/favicon.png differ
diff --git a/server/coverage/lcov-report/index.html b/server/coverage/lcov-report/index.html
new file mode 100644
index 00000000..79da4012
--- /dev/null
+++ b/server/coverage/lcov-report/index.html
@@ -0,0 +1,191 @@
+
+
+
+
+
+ Code coverage report for All files
+
+
+
+
+
+
+
+
+
+
+
+
All files
+
+
+
+ 92.16%
+ Statements
+ 247/268
+
+
+
+
+ 86.58%
+ Branches
+ 71/82
+
+
+
+
+ 96.96%
+ Functions
+ 32/33
+
+
+
+
+ 91.98%
+ Lines
+ 241/262
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b, p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+
+
+
+ | File |
+ |
+ Statements |
+ |
+ Branches |
+ |
+ Functions |
+ |
+ Lines |
+ |
+
+
+
+ | server |
+
+
+ |
+ 86.79% |
+ 46/53 |
+ 75% |
+ 6/8 |
+ 66.66% |
+ 2/3 |
+ 86.79% |
+ 46/53 |
+
+
+
+ | server/controllers |
+
+
+ |
+ 90.84% |
+ 129/142 |
+ 90% |
+ 54/60 |
+ 100% |
+ 14/14 |
+ 90.44% |
+ 123/136 |
+
+
+
+ | server/helpers |
+
+
+ |
+ 100% |
+ 13/13 |
+ 100% |
+ 0/0 |
+ 100% |
+ 4/4 |
+ 100% |
+ 13/13 |
+
+
+
+ | server/middlewares |
+
+
+ |
+ 100% |
+ 16/16 |
+ 100% |
+ 4/4 |
+ 100% |
+ 1/1 |
+ 100% |
+ 16/16 |
+
+
+
+ | server/models |
+
+
+ |
+ 97.43% |
+ 38/39 |
+ 70% |
+ 7/10 |
+ 100% |
+ 10/10 |
+ 97.43% |
+ 38/39 |
+
+
+
+ | server/routes |
+
+
+ |
+ 100% |
+ 5/5 |
+ 100% |
+ 0/0 |
+ 100% |
+ 1/1 |
+ 100% |
+ 5/5 |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/coverage/lcov-report/prettify.css b/server/coverage/lcov-report/prettify.css
new file mode 100644
index 00000000..b317a7cd
--- /dev/null
+++ b/server/coverage/lcov-report/prettify.css
@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
diff --git a/server/coverage/lcov-report/prettify.js b/server/coverage/lcov-report/prettify.js
new file mode 100644
index 00000000..b3225238
--- /dev/null
+++ b/server/coverage/lcov-report/prettify.js
@@ -0,0 +1,2 @@
+/* eslint-disable */
+window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^
+
+
+
+