Compare commits

..

34 Commits

Author SHA1 Message Date
jamesgeorge007
a3e941271e chore: address lint errors 2024-03-07 12:24:33 +05:30
jamesgeorge007
e7fded89d5 refactor: organize imports 2024-03-07 12:24:33 +05:30
jamesgeorge007
40c8d7e4b7 feat: persist bulk mode line wrap setting for request variables
- Show line wrap toggle only if entries are present under http request headers & URL encoded params body.
- Organize imports.
2024-03-07 12:24:33 +05:30
nivedin
1d93d9dabe refactor: filter non empty value for env presidence 2024-03-07 12:24:33 +05:30
nivedin
e9f1dc7ba1 chore: code refactors 2024-03-07 12:24:33 +05:30
nivedin
1e906bbec3 fix: code refactor and handle secret var in req var 2024-03-07 12:24:33 +05:30
nivedin
7776668e40 chore: align reqvariable tab to end 2024-03-07 12:24:33 +05:30
nivedin
3d38b7678b chore: aligh button in error placeholder 2024-03-07 12:24:33 +05:30
nivedin
1ca215d887 chore: add parent container for req variable codemirror 2024-03-07 12:24:33 +05:30
nivedin
aec04a13e3 chore: diable context menu for empty text 2024-03-07 12:24:33 +05:30
nivedin
9a2058bbec chore: update env hover colours 2024-03-07 12:24:33 +05:30
nivedin
1c65147a77 fix: autocomplete env bug 2024-03-07 12:24:33 +05:30
nivedin
207eea538f chore: check for active request variables 2024-03-07 12:24:33 +05:30
nivedin
e30b59e1b5 refactor: update environments colour schema 2024-03-07 12:24:33 +05:30
nivedin
9704b3324b chore: update env autocomplete 2024-03-07 12:24:33 +05:30
nivedin
386bb453d4 refactor: update environment variables precedence order 2024-03-07 12:24:33 +05:30
nivedin
175641246e refactor: update components for embeds flow 2024-03-07 12:24:33 +05:30
nivedin
fb615d2d2b chore: add env autocomplete to components 2024-03-07 12:24:33 +05:30
nivedin
7685409ee6 chore: update env tooltip updation mechanism 2024-03-07 12:24:33 +05:30
nivedin
e01818444e chore: add env autocomplete in smartenvinput 2024-03-07 12:24:33 +05:30
nivedin
52220d9a2e chore: add action for inscpector and tooltip env 2024-03-07 12:24:33 +05:30
nivedin
82d687f665 refactor: update insomnia import 2024-03-07 12:24:33 +05:30
nivedin
d44083a380 refactor: update openapi import 2024-03-07 12:24:33 +05:30
nivedin
7589c57e86 refactor: update postman import for request variable 2024-03-07 12:24:33 +05:30
nivedin
c8009aec77 chore: update test cases in cli 2024-03-07 12:24:33 +05:30
nivedin
7114f4fd43 chore: update test cases in app 2024-03-07 12:24:33 +05:30
nivedin
ca1a4ec31b refactor: update codegen and curlparser 2024-03-07 12:24:33 +05:30
nivedin
359633102e refactor: add check for request variables for env inspector 2024-03-07 12:24:33 +05:30
nivedin
b278691a9d chore: update history 2024-03-07 12:24:33 +05:30
nivedin
c27e1be230 refactor: update env tooltip 2024-03-07 12:24:33 +05:30
nivedin
11e8ba7bb3 refactor: add request variables to request runner flow 2024-03-07 12:24:33 +05:30
nivedin
2a0000cfc4 chore: remove unused file 2024-03-07 12:24:33 +05:30
nivedin
0ed063724e feat: add request variable 2024-03-07 12:24:33 +05:30
nivedin
170ec15821 refactor: add v2 request schema 2024-03-07 12:24:33 +05:30
66 changed files with 6193 additions and 3536 deletions

1
.npmrc
View File

@@ -1,2 +1 @@
shamefully-hoist=false shamefully-hoist=false
save-prefix=''

View File

@@ -23,13 +23,13 @@
"./packages/*" "./packages/*"
], ],
"devDependencies": { "devDependencies": {
"@commitlint/cli": "16.3.0", "@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "16.2.4", "@commitlint/config-conventional": "^16.2.1",
"@hoppscotch/ui": "0.1.0", "@hoppscotch/ui": "^0.1.0",
"@types/node": "17.0.27", "@types/node": "17.0.27",
"cross-env": "7.0.3", "cross-env": "^7.0.3",
"http-server": "14.1.1", "http-server": "^14.1.1",
"husky": "7.0.4", "husky": "^7.0.4",
"lint-staged": "12.4.0" "lint-staged": "12.4.0"
}, },
"pnpm": { "pnpm": {
@@ -37,7 +37,7 @@
"vue": "3.3.9" "vue": "3.3.9"
}, },
"packageExtensions": { "packageExtensions": {
"httpsnippet@3.0.1": { "httpsnippet@^3.0.1": {
"peerDependencies": { "peerDependencies": {
"ajv": "6.12.3" "ajv": "6.12.3"
} }

View File

@@ -17,16 +17,16 @@
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@codemirror/language": "6.10.1", "@codemirror/language": "6.9.3",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.0",
"@lezer/lr": "1.3.14" "@lezer/lr": "^1.3.14"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "1.5.1", "@lezer/generator": "^1.5.1",
"mocha": "9.2.2", "mocha": "^9.2.2",
"rollup": "3.29.4", "rollup": "^3.29.3",
"rollup-plugin-dts": "6.0.2", "rollup-plugin-dts": "^6.0.2",
"rollup-plugin-ts": "3.4.5", "rollup-plugin-ts": "^3.4.5",
"typescript": "5.2.2" "typescript": "^5.2.2"
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "hoppscotch-backend", "name": "hoppscotch-backend",
"version": "2024.3.0", "version": "2023.12.6",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -24,83 +24,83 @@
"do-test": "pnpm run test" "do-test": "pnpm run test"
}, },
"dependencies": { "dependencies": {
"@apollo/server": "4.9.5", "@apollo/server": "^4.9.4",
"@nestjs-modules/mailer": "1.9.1", "@nestjs-modules/mailer": "^1.9.1",
"@nestjs/apollo": "12.0.9", "@nestjs/apollo": "^12.0.9",
"@nestjs/common": "10.2.7", "@nestjs/common": "^10.2.6",
"@nestjs/config": "3.1.1", "@nestjs/config": "^3.1.1",
"@nestjs/core": "10.2.7", "@nestjs/core": "^10.2.6",
"@nestjs/graphql": "12.0.9", "@nestjs/graphql": "^12.0.9",
"@nestjs/jwt": "10.1.1", "@nestjs/jwt": "^10.1.1",
"@nestjs/passport": "10.0.2", "@nestjs/passport": "^10.0.2",
"@nestjs/platform-express": "10.2.7", "@nestjs/platform-express": "^10.2.6",
"@nestjs/schedule": "4.0.1", "@nestjs/schedule": "^4.0.1",
"@nestjs/throttler": "5.0.1", "@nestjs/throttler": "^5.0.0",
"@prisma/client": "5.8.1", "@prisma/client": "^5.8.0",
"argon2": "0.30.3", "argon2": "^0.30.3",
"bcrypt": "5.1.0", "bcrypt": "^5.1.0",
"cookie": "0.5.0", "cookie": "^0.5.0",
"cookie-parser": "1.4.6", "cookie-parser": "^1.4.6",
"cron": "3.1.6", "cron": "^3.1.6",
"express": "4.18.2", "express": "^4.17.1",
"express-session": "1.17.3", "express-session": "^1.17.3",
"fp-ts": "2.13.1", "fp-ts": "^2.13.1",
"graphql": "16.8.1", "graphql": "^16.8.1",
"graphql-query-complexity": "0.12.0", "graphql-query-complexity": "^0.12.0",
"graphql-redis-subscriptions": "2.6.0", "graphql-redis-subscriptions": "^2.6.0",
"graphql-subscriptions": "2.0.0", "graphql-subscriptions": "^2.0.0",
"handlebars": "4.7.7", "handlebars": "^4.7.7",
"io-ts": "2.2.16", "io-ts": "^2.2.16",
"luxon": "3.2.1", "luxon": "^3.2.1",
"nodemailer": "6.9.1", "nodemailer": "^6.9.1",
"passport": "0.6.0", "passport": "^0.6.0",
"passport-github2": "0.1.12", "passport-github2": "^0.1.12",
"passport-google-oauth20": "2.0.0", "passport-google-oauth20": "^2.0.0",
"passport-jwt": "4.0.1", "passport-jwt": "^4.0.1",
"passport-local": "1.0.0", "passport-local": "^1.0.0",
"passport-microsoft": "1.0.0", "passport-microsoft": "^1.0.0",
"posthog-node": "3.6.3", "posthog-node": "^3.6.3",
"prisma": "5.8.1", "prisma": "^5.8.0",
"reflect-metadata": "0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "3.0.2", "rimraf": "^3.0.2",
"rxjs": "7.6.0" "rxjs": "^7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "10.2.1", "@nestjs/cli": "^10.1.18",
"@nestjs/schematics": "10.0.3", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "10.2.7", "@nestjs/testing": "^10.2.6",
"@relmify/jest-fp-ts": "2.0.2", "@relmify/jest-fp-ts": "^2.0.2",
"@types/argon2": "0.15.0", "@types/argon2": "^0.15.0",
"@types/bcrypt": "5.0.0", "@types/bcrypt": "^5.0.0",
"@types/cookie": "0.5.1", "@types/cookie": "^0.5.1",
"@types/cookie-parser": "1.4.3", "@types/cookie-parser": "^1.4.3",
"@types/express": "4.17.14", "@types/express": "^4.17.14",
"@types/jest": "29.4.0", "@types/jest": "^29.4.0",
"@types/luxon": "3.2.0", "@types/luxon": "^3.2.0",
"@types/node": "18.11.10", "@types/node": "^18.11.10",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "^6.4.7",
"@types/passport-github2": "1.2.5", "@types/passport-github2": "^1.2.5",
"@types/passport-google-oauth20": "2.0.11", "@types/passport-google-oauth20": "^2.0.11",
"@types/passport-jwt": "3.0.8", "@types/passport-jwt": "^3.0.8",
"@types/passport-microsoft": "0.0.0", "@types/passport-microsoft": "^0.0.0",
"@types/supertest": "2.0.12", "@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "5.45.0", "@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "5.45.0", "@typescript-eslint/parser": "^5.45.0",
"cross-env": "7.0.3", "cross-env": "^7.0.3",
"eslint": "8.29.0", "eslint": "^8.29.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "^4.2.1",
"jest": "29.4.1", "jest": "^29.4.1",
"jest-mock-extended": "3.0.1", "jest-mock-extended": "^3.0.1",
"jwt": "link:@types/nestjs/jwt", "jwt": "link:@types/nestjs/jwt",
"prettier": "2.8.4", "prettier": "^2.8.4",
"source-map-support": "0.5.21", "source-map-support": "^0.5.21",
"supertest": "6.3.2", "supertest": "^6.3.2",
"ts-jest": "29.0.5", "ts-jest": "29.0.5",
"ts-loader": "9.4.2", "ts-loader": "^9.4.2",
"ts-node": "10.9.1", "ts-node": "^10.9.1",
"tsconfig-paths": "4.1.1", "tsconfig-paths": "4.1.1",
"typescript": "4.9.3" "typescript": "^4.9.3"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [
@@ -121,4 +121,4 @@
"^src/(.*)$": "<rootDir>/$1" "^src/(.*)$": "<rootDir>/$1"
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.7.0", "version": "0.6.0",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"type": "module", "type": "module",
@@ -41,28 +41,30 @@
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"dependencies": { "dependencies": {
"axios": "1.6.7", "axios": "^1.6.6",
"chalk": "5.3.0", "chalk": "^5.3.0",
"commander": "11.1.0", "commander": "^11.1.0",
"lodash-es": "4.17.21", "lodash-es": "^4.17.21",
"qs": "6.11.2", "qs": "^6.11.2",
"verzod": "0.2.2", "zod": "^3.22.4"
"zod": "3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@relmify/jest-fp-ts": "2.1.1", "@relmify/jest-fp-ts": "^2.1.1",
"@swc/core": "1.4.2", "@swc/core": "^1.3.105",
"@types/jest": "29.5.12", "@types/jest": "^29.5.11",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "^4.17.12",
"@types/qs": "6.9.12", "@types/qs": "^6.9.11",
"fp-ts": "2.16.2", "fp-ts": "^2.16.2",
"jest": "29.7.0", "jest": "^29.7.0",
"prettier": "3.2.5", "lodash": "^4.17.21",
"qs": "6.11.2", "prettier": "^3.2.4",
"ts-jest": "29.1.2", "qs": "^6.11.2",
"tsup": "8.0.2", "ts-jest": "^29.1.2",
"typescript": "5.3.3" "tsup": "^8.0.1",
"typescript": "^5.3.3",
"verzod": "^0.2.2",
"zod": "^3.22.4"
} }
} }

View File

@@ -154,7 +154,7 @@
"invalid_name": "Please provide a name for the collection", "invalid_name": "Please provide a name for the collection",
"invalid_root_move": "Collection already in the root", "invalid_root_move": "Collection already in the root",
"moved": "Moved Successfully", "moved": "Moved Successfully",
"my_collections": "Personal Collections", "my_collections": "My Collections",
"name": "My New Collection", "name": "My New Collection",
"name_length_insufficient": "Collection name should be at least 3 characters long", "name_length_insufficient": "Collection name should be at least 3 characters long",
"new": "New Collection", "new": "New Collection",
@@ -166,12 +166,14 @@
"save_as": "Save as", "save_as": "Save as",
"save_to_collection": "Save to Collection", "save_to_collection": "Save to Collection",
"select": "Select a Collection", "select": "Select a Collection",
"select_location": "Select location" "select_location": "Select location",
"select_team": "Select a team",
"team_collections": "Team Collections"
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?", "close_unsaved_tab": "Are you sure you want to close this tab?",
"close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.", "close_unsaved_tabs": "Are you sure you want to close all tabs? {count} unsaved tabs will be lost.",
"exit_team": "Are you sure you want to leave this workspace?", "exit_team": "Are you sure you want to leave this team?",
"logout": "Are you sure you want to logout?", "logout": "Are you sure you want to logout?",
"remove_collection": "Are you sure you want to permanently delete this collection?", "remove_collection": "Are you sure you want to permanently delete this collection?",
"remove_environment": "Are you sure you want to permanently delete this environment?", "remove_environment": "Are you sure you want to permanently delete this environment?",
@@ -179,7 +181,7 @@
"remove_history": "Are you sure you want to permanently delete all history?", "remove_history": "Are you sure you want to permanently delete all history?",
"remove_request": "Are you sure you want to permanently delete this request?", "remove_request": "Are you sure you want to permanently delete this request?",
"remove_shared_request": "Are you sure you want to permanently delete this shared request?", "remove_shared_request": "Are you sure you want to permanently delete this shared request?",
"remove_team": "Are you sure you want to delete this workspace?", "remove_team": "Are you sure you want to delete this team?",
"remove_telemetry": "Are you sure you want to opt-out of Telemetry?", "remove_telemetry": "Are you sure you want to opt-out of Telemetry?",
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.", "request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
"save_unsaved_tab": "Do you want to save changes made in this tab?", "save_unsaved_tab": "Do you want to save changes made in this tab?",
@@ -232,9 +234,9 @@
"headers": "This request does not have any headers", "headers": "This request does not have any headers",
"history": "History is empty", "history": "History is empty",
"invites": "Invite list is empty", "invites": "Invite list is empty",
"members": "Workspace is empty", "members": "Team is empty",
"parameters": "This request does not have any parameters", "parameters": "This request does not have any parameters",
"pending_invites": "There are no pending invites for this workspace", "pending_invites": "There are no pending invites for this team",
"profile": "Login to view your profile", "profile": "Login to view your profile",
"protocols": "Protocols are empty", "protocols": "Protocols are empty",
"request_variables": "This request does not have any request variables", "request_variables": "This request does not have any request variables",
@@ -243,8 +245,8 @@
"shared_requests": "Shared requests are empty", "shared_requests": "Shared requests are empty",
"shared_requests_logout": "Login to view your shared requests or create a new one", "shared_requests_logout": "Login to view your shared requests or create a new one",
"subscription": "Subscriptions are empty", "subscription": "Subscriptions are empty",
"team_name": "Workspace name empty", "team_name": "Team name empty",
"teams": "You don't belong to any workspaces", "teams": "You don't belong to any teams",
"tests": "There are no tests for this request" "tests": "There are no tests for this request"
}, },
"environment": { "environment": {
@@ -261,7 +263,7 @@
"import_or_create": "Import or create a environment", "import_or_create": "Import or create a environment",
"invalid_name": "Please provide a name for the environment", "invalid_name": "Please provide a name for the environment",
"list": "Environment variables", "list": "Environment variables",
"my_environments": "Personal Environments", "my_environments": "My Environments",
"name": "Name", "name": "Name",
"nested_overflow": "nested environment variables are limited to 10 levels", "nested_overflow": "nested environment variables are limited to 10 levels",
"new": "New Environment", "new": "New Environment",
@@ -276,7 +278,7 @@
"select": "Select environment", "select": "Select environment",
"set": "Set environment", "set": "Set environment",
"set_as_environment": "Set as environment", "set_as_environment": "Set as environment",
"team_environments": "Workspace Environments", "team_environments": "Team Environments",
"title": "Environments", "title": "Environments",
"updated": "Environment updated", "updated": "Environment updated",
"value": "Value", "value": "Value",
@@ -291,8 +293,8 @@
"check_how_to_add_origin": "Check how you can add an origin", "check_how_to_add_origin": "Check how you can add an origin",
"curl_invalid_format": "cURL is not formatted properly", "curl_invalid_format": "cURL is not formatted properly",
"danger_zone": "Danger zone", "danger_zone": "Danger zone",
"delete_account": "Your account is currently an owner in these workspaces:", "delete_account": "Your account is currently an owner in these teams:",
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these workspaces before you can delete your account.", "delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
"empty_req_name": "Empty Request Name", "empty_req_name": "Empty Request Name",
"f12_details": "(F12 for details)", "f12_details": "(F12 for details)",
"gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again", "gql_prettify_invalid_query": "Couldn't prettify an invalid query, solve query syntax errors and try again",
@@ -394,8 +396,8 @@
"from_insomnia_description": "Import from Insomnia collection", "from_insomnia_description": "Import from Insomnia collection",
"from_json": "Import from Hoppscotch", "from_json": "Import from Hoppscotch",
"from_json_description": "Import from Hoppscotch collection file", "from_json_description": "Import from Hoppscotch collection file",
"from_my_collections": "Import from Personal Collections", "from_my_collections": "Import from My Collections",
"from_my_collections_description": "Import from Personal Collections file", "from_my_collections_description": "Import from My Collections file",
"from_openapi": "Import from OpenAPI", "from_openapi": "Import from OpenAPI",
"from_openapi_description": "Import from OpenAPI specification file (YML/JSON)", "from_openapi_description": "Import from OpenAPI specification file (YML/JSON)",
"from_postman": "Import from Postman", "from_postman": "Import from Postman",
@@ -512,7 +514,7 @@
"email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.", "email_verification_mail": "A verification email has been sent to your email address. Please click on the link to verify your email address.",
"no_permission": "You do not have permission to perform this action.", "no_permission": "You do not have permission to perform this action.",
"owner": "Owner", "owner": "Owner",
"owner_description": "Owners can add, edit, and delete requests, collections and workspace members.", "owner_description": "Owners can add, edit, and delete requests, collections and team members.",
"roles": "Roles", "roles": "Roles",
"roles_description": "Roles are used to control access to the shared collections.", "roles_description": "Roles are used to control access to the shared collections.",
"updated": "Profile updated", "updated": "Profile updated",
@@ -817,12 +819,12 @@
"title": "Tabs" "title": "Tabs"
}, },
"workspace": { "workspace": {
"delete": "Delete current workspace", "delete": "Delete current team",
"edit": "Edit current workspace", "edit": "Edit current team",
"invite": "Invite people to workspace", "invite": "Invite people to team",
"new": "Create new workspace", "new": "Create new team",
"switch_to_personal": "Switch to your personal workspace", "switch_to_personal": "Switch to your personal workspace",
"title": "Workspaces" "title": "Teams"
} }
}, },
"sse": { "sse": {
@@ -879,6 +881,7 @@
"forum": "Ask questions and get answers", "forum": "Ask questions and get answers",
"github": "Follow us on Github", "github": "Follow us on Github",
"shortcuts": "Browse app faster", "shortcuts": "Browse app faster",
"team": "Get in touch with the team",
"title": "Support", "title": "Support",
"twitter": "Follow us on Twitter" "twitter": "Follow us on Twitter"
}, },
@@ -909,59 +912,60 @@
"websocket": "WebSocket" "websocket": "WebSocket"
}, },
"team": { "team": {
"already_member": "You are already a member of this workspace. Contact your workspace owner.", "already_member": "You are already a member of this team. Contact your team owner.",
"create_new": "Create new workspace", "create_new": "Create new team",
"deleted": "Workspace deleted", "deleted": "Team deleted",
"edit": "Edit Workspace", "edit": "Edit Team",
"email": "E-mail", "email": "E-mail",
"email_do_not_match": "Email doesn't match with your account details. Contact your workspace owner.", "email_do_not_match": "Email doesn't match with your account details. Contact your team owner.",
"exit": "Exit Workspace", "exit": "Exit Team",
"exit_disabled": "Only owner cannot exit the workspace", "exit_disabled": "Only owner cannot exit the team",
"failed_invites": "Failed invites", "failed_invites": "Failed invites",
"invalid_coll_id": "Invalid collection ID", "invalid_coll_id": "Invalid collection ID",
"invalid_email_format": "Email format is invalid", "invalid_email_format": "Email format is invalid",
"invalid_id": "Invalid workspace ID. Contact your workspace owner.", "invalid_id": "Invalid team ID. Contact your team owner.",
"invalid_invite_link": "Invalid invite link", "invalid_invite_link": "Invalid invite link",
"invalid_invite_link_description": "The link you followed is invalid. Contact your workspace owner.", "invalid_invite_link_description": "The link you followed is invalid. Contact your team owner.",
"invalid_member_permission": "Please provide a valid permission to the workspace member", "invalid_member_permission": "Please provide a valid permission to the team member",
"invite": "Invite", "invite": "Invite",
"invite_more": "Invite more", "invite_more": "Invite more",
"invite_tooltip": "Invite people to this workspace", "invite_tooltip": "Invite people to this workspace",
"invited_to_team": "{owner} invited you to join {workspace}", "invited_to_team": "{owner} invited you to join {team}",
"join": "Invitation accepted", "join": "Invitation accepted",
"join_team": "Join {workspace}", "join_beta": "Join the beta program to access teams.",
"joined_team": "You have joined {workspace}", "join_team": "Join {team}",
"joined_team_description": "You are now a member of this workspace", "joined_team": "You have joined {team}",
"left": "You left the workspace", "joined_team_description": "You are now a member of this team",
"left": "You left the team",
"login_to_continue": "Login to continue", "login_to_continue": "Login to continue",
"login_to_continue_description": "You need to be logged in to join a workspace.", "login_to_continue_description": "You need to be logged in to join a team.",
"logout_and_try_again": "Logout and sign in with another account", "logout_and_try_again": "Logout and sign in with another account",
"member_has_invite": "This email ID already has an invite. Contact your workspace owner.", "member_has_invite": "This email ID already has an invite. Contact your team owner.",
"member_not_found": "Member not found. Contact your workspace owner.", "member_not_found": "Member not found. Contact your team owner.",
"member_removed": "User removed", "member_removed": "User removed",
"member_role_updated": "User roles updated", "member_role_updated": "User roles updated",
"members": "Members", "members": "Members",
"more_members": "+{count} more", "more_members": "+{count} more",
"name_length_insufficient": "Workspace name should be at least 6 characters long", "name_length_insufficient": "Team name should be at least 6 characters long",
"name_updated": "Workspace name updated", "name_updated": "Team name updated",
"new": "New Workspace", "new": "New Team",
"new_created": "New workspace created", "new_created": "New team created",
"new_name": "My New Workspace", "new_name": "My New Team",
"no_access": "You do not have edit access to this workspace", "no_access": "You do not have edit access to this team",
"no_invite_found": "Invitation not found. Contact your workspace owner.", "no_invite_found": "Invitation not found. Contact your team owner.",
"no_request_found": "Request not found.", "no_request_found": "Request not found.",
"not_found": "Workspace not found. Contact your workspace owner.", "not_found": "Team not found. Contact your team owner.",
"not_valid_viewer": "You are not a valid viewer. Contact your workspace owner.", "not_valid_viewer": "You are not a valid viewer. Contact your team owner.",
"parent_coll_move": "Cannot move collection to a child collection", "parent_coll_move": "Cannot move collection to a child collection",
"pending_invites": "Pending invites", "pending_invites": "Pending invites",
"permissions": "Permissions", "permissions": "Permissions",
"same_target_destination": "Same target and destination", "same_target_destination": "Same target and destination",
"saved": "Workspace saved", "saved": "Team saved",
"select_a_team": "Select a workspace", "select_a_team": "Select a team",
"success_invites": "Success invites", "success_invites": "Success invites",
"title": "Workspaces", "title": "Teams",
"we_sent_invite_link": "We sent an invite link to all invitees!", "we_sent_invite_link": "We sent an invite link to all invitees!",
"we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the workspace." "we_sent_invite_link_description": "Ask all invitees to check their inbox. Click on the link to join the team."
}, },
"team_environment": { "team_environment": {
"deleted": "Environment Deleted", "deleted": "Environment Deleted",
@@ -987,14 +991,8 @@
}, },
"workspace": { "workspace": {
"change": "Change workspace", "change": "Change workspace",
"personal": "Personal Workspace", "personal": "My Workspace",
"other_workspaces": "My Workspaces", "team": "Team Workspace",
"team": "Workspace",
"title": "Workspaces" "title": "Workspaces"
},
"site_protection": {
"login_to_continue": "Login to continue",
"login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance.",
"error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status"
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/common", "name": "@hoppscotch/common",
"private": true, "private": true,
"version": "2024.3.0", "version": "2023.12.6",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run", "test": "vitest --run",
@@ -21,147 +21,147 @@
"do-lintfix": "pnpm run lintfix" "do-lintfix": "pnpm run lintfix"
}, },
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "10.1.0", "@apidevtools/swagger-parser": "^10.1.0",
"@codemirror/autocomplete": "6.13.0", "@codemirror/autocomplete": "^6.11.1",
"@codemirror/commands": "6.3.3", "@codemirror/commands": "^6.3.2",
"@codemirror/lang-javascript": "6.2.2", "@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-json": "6.0.1", "@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-xml": "6.1.0", "@codemirror/lang-xml": "^6.0.2",
"@codemirror/language": "6.10.1", "@codemirror/language": "6.9.3",
"@codemirror/legacy-modes": "6.3.3", "@codemirror/legacy-modes": "^6.3.3",
"@codemirror/lint": "6.5.0", "@codemirror/lint": "^6.4.2",
"@codemirror/search": "6.5.6", "@codemirror/search": "^6.5.5",
"@codemirror/state": "6.4.1", "@codemirror/state": "^6.3.3",
"@codemirror/view": "6.25.1", "@codemirror/view": "^6.22.3",
"@hoppscotch/codemirror-lang-graphql": "workspace:^", "@hoppscotch/codemirror-lang-graphql": "workspace:^",
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@hoppscotch/ui": "0.1.0", "@hoppscotch/ui": "^0.1.0",
"@hoppscotch/vue-toasted": "0.1.0", "@hoppscotch/vue-toasted": "^0.1.0",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.0",
"@unhead/vue": "1.8.8", "@unhead/vue": "^1.8.8",
"@urql/core": "4.2.0", "@urql/core": "^4.2.0",
"@urql/devtools": "2.0.3", "@urql/devtools": "^2.0.3",
"@urql/exchange-auth": "2.1.6", "@urql/exchange-auth": "^2.1.6",
"@urql/exchange-graphcache": "6.4.0", "@urql/exchange-graphcache": "^6.3.3",
"@vitejs/plugin-legacy": "4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
"@vueuse/core": "10.7.0", "@vueuse/core": "^10.6.1",
"acorn-walk": "8.3.0", "acorn-walk": "^8.3.0",
"axios": "1.6.2", "axios": "^1.6.2",
"buffer": "6.0.3", "buffer": "^6.0.3",
"cookie-es": "1.0.0", "cookie-es": "^1.0.0",
"dioc": "1.0.1", "dioc": "^1.0.1",
"esprima": "4.0.1", "esprima": "^4.0.1",
"events": "3.3.0", "events": "^3.3.0",
"fp-ts": "2.16.1", "fp-ts": "^2.16.1",
"globalthis": "1.0.3", "globalthis": "^1.0.3",
"graphql": "16.8.1", "graphql": "^16.8.1",
"graphql-language-service-interface": "2.10.2", "graphql-language-service-interface": "^2.10.2",
"graphql-tag": "2.12.6", "graphql-tag": "^2.12.6",
"httpsnippet": "3.0.1", "httpsnippet": "^3.0.1",
"insomnia-importers": "3.6.0", "insomnia-importers": "^3.6.0",
"io-ts": "2.2.20", "io-ts": "^2.2.20",
"js-yaml": "4.1.0", "js-yaml": "^4.1.0",
"jsonpath-plus": "7.2.0", "jsonpath-plus": "^7.2.0",
"lodash-es": "4.17.21", "lodash-es": "^4.17.21",
"lossless-json": "3.0.2", "lossless-json": "^3.0.2",
"minisearch": "6.3.0", "minisearch": "^6.3.0",
"nprogress": "0.2.0", "nprogress": "^0.2.0",
"paho-mqtt": "1.1.0", "paho-mqtt": "^1.1.0",
"path": "0.12.7", "path": "^0.12.7",
"postman-collection": "4.3.0", "postman-collection": "^4.3.0",
"process": "0.11.10", "process": "^0.11.10",
"qs": "6.11.2", "qs": "^6.11.2",
"quicktype-core": "23.0.79", "quicktype-core": "^23.0.79",
"rxjs": "7.8.1", "rxjs": "^7.8.1",
"set-cookie-parser": "2.6.0", "set-cookie-parser": "^2.6.0",
"set-cookie-parser-es": "1.0.5", "set-cookie-parser-es": "^1.0.5",
"socket.io-client-v2": "npm:socket.io-client@2.4.0", "socket.io-client-v2": "npm:socket.io-client@^2.4.0",
"socket.io-client-v3": "npm:socket.io-client@3.1.3", "socket.io-client-v3": "npm:socket.io-client@^3.1.3",
"socket.io-client-v4": "npm:socket.io-client@4.4.1", "socket.io-client-v4": "npm:socket.io-client@^4.4.1",
"socketio-wildcard": "2.0.0", "socketio-wildcard": "^2.0.0",
"splitpanes": "3.1.5", "splitpanes": "^3.1.5",
"stream-browserify": "3.0.0", "stream-browserify": "^3.0.0",
"subscriptions-transport-ws": "0.11.0", "subscriptions-transport-ws": "^0.11.0",
"tern": "0.24.3", "tern": "^0.24.3",
"timers": "0.1.1", "timers": "^0.1.1",
"tippy.js": "6.3.7", "tippy.js": "^6.3.7",
"url": "0.11.3", "url": "^0.11.3",
"util": "0.12.5", "util": "^0.12.5",
"uuid": "9.0.1", "uuid": "^9.0.1",
"verzod": "0.2.2", "verzod": "^0.2.0",
"vue": "3.3.9", "vue": "^3.3.8",
"vue-i18n": "9.8.0", "vue-i18n": "^9.7.1",
"vue-pdf-embed": "1.2.1", "vue-pdf-embed": "^1.2.1",
"vue-router": "4.2.5", "vue-router": "^4.2.5",
"vue-tippy": "6.3.1", "vue-tippy": "6.3.1",
"vuedraggable-es": "4.1.1", "vuedraggable-es": "^4.1.1",
"wonka": "6.3.4", "wonka": "^6.3.4",
"workbox-window": "7.0.0", "workbox-window": "^7.0.0",
"xml-formatter": "3.6.0", "xml-formatter": "^3.6.0",
"yargs-parser": "21.1.1", "yargs-parser": "^21.1.1",
"zod": "3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@esbuild-plugins/node-modules-polyfill": "0.2.2", "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@graphql-codegen/add": "5.0.0", "@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "5.0.0", "@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "^5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-urql-graphcache": "3.0.0", "@graphql-codegen/typescript-urql-graphcache": "^3.0.0",
"@graphql-codegen/urql-introspection": "3.0.0", "@graphql-codegen/urql-introspection": "^3.0.0",
"@graphql-typed-document-node/core": "3.2.0", "@graphql-typed-document-node/core": "^3.2.0",
"@iconify-json/lucide": "1.1.144", "@iconify-json/lucide": "^1.1.141",
"@intlify/vite-plugin-vue-i18n": "7.0.0", "@intlify/vite-plugin-vue-i18n": "^7.0.0",
"@relmify/jest-fp-ts": "2.1.1", "@relmify/jest-fp-ts": "^2.1.1",
"@rushstack/eslint-patch": "1.6.0", "@rushstack/eslint-patch": "^1.6.0",
"@types/har-format": "1.2.15", "@types/har-format": "^1.2.15",
"@types/js-yaml": "4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "^4.17.12",
"@types/lossless-json": "1.0.4", "@types/lossless-json": "^1.0.4",
"@types/nprogress": "0.2.3", "@types/nprogress": "^0.2.3",
"@types/paho-mqtt": "1.0.10", "@types/paho-mqtt": "^1.0.10",
"@types/postman-collection": "3.5.10", "@types/postman-collection": "^3.5.10",
"@types/splitpanes": "2.2.6", "@types/splitpanes": "^2.2.6",
"@types/uuid": "9.0.7", "@types/uuid": "^9.0.7",
"@types/yargs-parser": "21.0.3", "@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "6.13.2", "@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "6.13.2", "@typescript-eslint/parser": "^6.12.0",
"@vitejs/plugin-vue": "4.5.1", "@vitejs/plugin-vue": "^4.5.0",
"@vue/compiler-sfc": "3.3.10", "@vue/compiler-sfc": "^3.3.8",
"@vue/eslint-config-typescript": "12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"@vue/runtime-core": "3.3.10", "@vue/runtime-core": "^3.3.8",
"autoprefixer": "10.4.16", "autoprefixer": "^10.4.14",
"cross-env": "7.0.3", "cross-env": "^7.0.3",
"dotenv": "16.3.1", "dotenv": "^16.3.1",
"eslint": "8.55.0", "eslint": "^8.54.0",
"eslint-plugin-prettier": "5.0.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "9.19.2", "eslint-plugin-vue": "^9.18.1",
"glob": "10.3.10", "glob": "^10.3.10",
"npm-run-all": "4.1.5", "npm-run-all": "^4.1.5",
"openapi-types": "12.1.3", "openapi-types": "^12.1.3",
"postcss": "8.4.31", "postcss": "^8.4.23",
"prettier": "3.1.0", "prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "0.5.7", "prettier-plugin-tailwindcss": "^0.5.7",
"rollup-plugin-polyfill-node": "0.13.0", "rollup-plugin-polyfill-node": "^0.13.0",
"sass": "1.69.5", "sass": "^1.69.5",
"tailwindcss": "3.3.5", "tailwindcss": "^3.3.2",
"typescript": "5.3.2", "typescript": "^5.3.2",
"unplugin-fonts": "1.1.1", "unplugin-fonts": "^1.1.1",
"unplugin-icons": "0.17.4", "unplugin-icons": "^0.17.4",
"unplugin-vue-components": "0.25.2", "unplugin-vue-components": "^0.25.2",
"vite": "4.5.0", "vite": "^4.5.0",
"vite-plugin-checker": "0.6.2", "vite-plugin-checker": "^0.6.2",
"vite-plugin-fonts": "0.7.0", "vite-plugin-fonts": "^0.7.0",
"vite-plugin-html-config": "1.0.11", "vite-plugin-html-config": "^1.0.11",
"vite-plugin-inspect": "0.7.42", "vite-plugin-inspect": "^0.7.42",
"vite-plugin-pages": "0.31.0", "vite-plugin-pages": "^0.31.0",
"vite-plugin-pages-sitemap": "1.6.1", "vite-plugin-pages-sitemap": "^1.6.1",
"vite-plugin-pwa": "0.17.3", "vite-plugin-pwa": "^0.17.0",
"vite-plugin-vue-layouts": "0.8.0", "vite-plugin-vue-layouts": "^0.8.0",
"vitest": "0.34.6", "vitest": "^0.34.6",
"vue-tsc": "1.8.24" "vue-tsc": "^1.8.22"
} }
} }

View File

@@ -1,214 +1,213 @@
// generated by unplugin-vue-components // generated by unplugin-vue-components
// We suggest you to commit this file into source control // We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399 // Read more: https://github.com/vuejs/core/pull/3399
import "@vue/runtime-core" import '@vue/runtime-core'
export {} export {}
declare module "@vue/runtime-core" { declare module '@vue/runtime-core' {
export interface GlobalComponents { export interface GlobalComponents {
AppActionHandler: (typeof import("./components/app/ActionHandler.vue"))["default"] AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
AppBanner: (typeof import("./components/app/Banner.vue"))["default"] AppBanner: typeof import('./components/app/Banner.vue')['default']
AppContextMenu: (typeof import("./components/app/ContextMenu.vue"))["default"] AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
AppDeveloperOptions: (typeof import("./components/app/DeveloperOptions.vue"))["default"] AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
AppFooter: (typeof import("./components/app/Footer.vue"))["default"] AppFooter: typeof import('./components/app/Footer.vue')['default']
AppGitHubStarButton: (typeof import("./components/app/GitHubStarButton.vue"))["default"] AppGitHubStarButton: typeof import('./components/app/GitHubStarButton.vue')['default']
AppHeader: (typeof import("./components/app/Header.vue"))["default"] AppHeader: typeof import('./components/app/Header.vue')['default']
AppInspection: (typeof import("./components/app/Inspection.vue"))["default"] AppInspection: typeof import('./components/app/Inspection.vue')['default']
AppInterceptor: (typeof import("./components/app/Interceptor.vue"))["default"] AppInterceptor: typeof import('./components/app/Interceptor.vue')['default']
AppLogo: (typeof import("./components/app/Logo.vue"))["default"] AppLogo: typeof import('./components/app/Logo.vue')['default']
AppOptions: (typeof import("./components/app/Options.vue"))["default"] AppOptions: typeof import('./components/app/Options.vue')['default']
AppPaneLayout: (typeof import("./components/app/PaneLayout.vue"))["default"] AppPaneLayout: typeof import('./components/app/PaneLayout.vue')['default']
AppShare: (typeof import("./components/app/Share.vue"))["default"] AppShare: typeof import('./components/app/Share.vue')['default']
AppShortcuts: (typeof import("./components/app/Shortcuts.vue"))["default"] AppShortcuts: typeof import('./components/app/Shortcuts.vue')['default']
AppShortcutsEntry: (typeof import("./components/app/ShortcutsEntry.vue"))["default"] AppShortcutsEntry: typeof import('./components/app/ShortcutsEntry.vue')['default']
AppShortcutsPrompt: (typeof import("./components/app/ShortcutsPrompt.vue"))["default"] AppShortcutsPrompt: typeof import('./components/app/ShortcutsPrompt.vue')['default']
AppSidenav: (typeof import("./components/app/Sidenav.vue"))["default"] AppSidenav: typeof import('./components/app/Sidenav.vue')['default']
AppSpotlight: (typeof import("./components/app/spotlight/index.vue"))["default"] AppSpotlight: typeof import('./components/app/spotlight/index.vue')['default']
AppSpotlightEntry: (typeof import("./components/app/spotlight/Entry.vue"))["default"] AppSpotlightEntry: typeof import('./components/app/spotlight/Entry.vue')['default']
AppSpotlightEntryGQLHistory: (typeof import("./components/app/spotlight/entry/GQLHistory.vue"))["default"] AppSpotlightEntryGQLHistory: typeof import('./components/app/spotlight/entry/GQLHistory.vue')['default']
AppSpotlightEntryGQLRequest: (typeof import("./components/app/spotlight/entry/GQLRequest.vue"))["default"] AppSpotlightEntryGQLRequest: typeof import('./components/app/spotlight/entry/GQLRequest.vue')['default']
AppSpotlightEntryIconSelected: (typeof import("./components/app/spotlight/entry/IconSelected.vue"))["default"] AppSpotlightEntryIconSelected: typeof import('./components/app/spotlight/entry/IconSelected.vue')['default']
AppSpotlightEntryRESTHistory: (typeof import("./components/app/spotlight/entry/RESTHistory.vue"))["default"] AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
AppSpotlightEntryRESTRequest: (typeof import("./components/app/spotlight/entry/RESTRequest.vue"))["default"] AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
AppSupport: (typeof import("./components/app/Support.vue"))["default"] AppSupport: typeof import('./components/app/Support.vue')['default']
Collections: (typeof import("./components/collections/index.vue"))["default"] Collections: typeof import('./components/collections/index.vue')['default']
CollectionsAdd: (typeof import("./components/collections/Add.vue"))["default"] CollectionsAdd: typeof import('./components/collections/Add.vue')['default']
CollectionsAddFolder: (typeof import("./components/collections/AddFolder.vue"))["default"] CollectionsAddFolder: typeof import('./components/collections/AddFolder.vue')['default']
CollectionsAddRequest: (typeof import("./components/collections/AddRequest.vue"))["default"] CollectionsAddRequest: typeof import('./components/collections/AddRequest.vue')['default']
CollectionsCollection: (typeof import("./components/collections/Collection.vue"))["default"] CollectionsCollection: typeof import('./components/collections/Collection.vue')['default']
CollectionsEdit: (typeof import("./components/collections/Edit.vue"))["default"] CollectionsEdit: typeof import('./components/collections/Edit.vue')['default']
CollectionsEditFolder: (typeof import("./components/collections/EditFolder.vue"))["default"] CollectionsEditFolder: typeof import('./components/collections/EditFolder.vue')['default']
CollectionsEditRequest: (typeof import("./components/collections/EditRequest.vue"))["default"] CollectionsEditRequest: typeof import('./components/collections/EditRequest.vue')['default']
CollectionsGraphql: (typeof import("./components/collections/graphql/index.vue"))["default"] CollectionsGraphql: typeof import('./components/collections/graphql/index.vue')['default']
CollectionsGraphqlAdd: (typeof import("./components/collections/graphql/Add.vue"))["default"] CollectionsGraphqlAdd: typeof import('./components/collections/graphql/Add.vue')['default']
CollectionsGraphqlAddFolder: (typeof import("./components/collections/graphql/AddFolder.vue"))["default"] CollectionsGraphqlAddFolder: typeof import('./components/collections/graphql/AddFolder.vue')['default']
CollectionsGraphqlAddRequest: (typeof import("./components/collections/graphql/AddRequest.vue"))["default"] CollectionsGraphqlAddRequest: typeof import('./components/collections/graphql/AddRequest.vue')['default']
CollectionsGraphqlCollection: (typeof import("./components/collections/graphql/Collection.vue"))["default"] CollectionsGraphqlCollection: typeof import('./components/collections/graphql/Collection.vue')['default']
CollectionsGraphqlEdit: (typeof import("./components/collections/graphql/Edit.vue"))["default"] CollectionsGraphqlEdit: typeof import('./components/collections/graphql/Edit.vue')['default']
CollectionsGraphqlEditFolder: (typeof import("./components/collections/graphql/EditFolder.vue"))["default"] CollectionsGraphqlEditFolder: typeof import('./components/collections/graphql/EditFolder.vue')['default']
CollectionsGraphqlEditRequest: (typeof import("./components/collections/graphql/EditRequest.vue"))["default"] CollectionsGraphqlEditRequest: typeof import('./components/collections/graphql/EditRequest.vue')['default']
CollectionsGraphqlFolder: (typeof import("./components/collections/graphql/Folder.vue"))["default"] CollectionsGraphqlFolder: typeof import('./components/collections/graphql/Folder.vue')['default']
CollectionsGraphqlImportExport: (typeof import("./components/collections/graphql/ImportExport.vue"))["default"] CollectionsGraphqlImportExport: typeof import('./components/collections/graphql/ImportExport.vue')['default']
CollectionsGraphqlRequest: (typeof import("./components/collections/graphql/Request.vue"))["default"] CollectionsGraphqlRequest: typeof import('./components/collections/graphql/Request.vue')['default']
CollectionsImportExport: (typeof import("./components/collections/ImportExport.vue"))["default"] CollectionsImportExport: typeof import('./components/collections/ImportExport.vue')['default']
CollectionsMyCollections: (typeof import("./components/collections/MyCollections.vue"))["default"] CollectionsMyCollections: typeof import('./components/collections/MyCollections.vue')['default']
CollectionsProperties: (typeof import("./components/collections/Properties.vue"))["default"] CollectionsProperties: typeof import('./components/collections/Properties.vue')['default']
CollectionsRequest: (typeof import("./components/collections/Request.vue"))["default"] CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
CollectionsSaveRequest: (typeof import("./components/collections/SaveRequest.vue"))["default"] CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
CollectionsTeamCollections: (typeof import("./components/collections/TeamCollections.vue"))["default"] CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
CookiesAllModal: (typeof import("./components/cookies/AllModal.vue"))["default"] CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
CookiesEditCookie: (typeof import("./components/cookies/EditCookie.vue"))["default"] CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
Embeds: (typeof import("./components/embeds/index.vue"))["default"] Embeds: typeof import('./components/embeds/index.vue')['default']
Environments: (typeof import("./components/environments/index.vue"))["default"] Environments: typeof import('./components/environments/index.vue')['default']
EnvironmentsAdd: (typeof import("./components/environments/Add.vue"))["default"] EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
EnvironmentsImportExport: (typeof import("./components/environments/ImportExport.vue"))["default"] EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
EnvironmentsMy: (typeof import("./components/environments/my/index.vue"))["default"] EnvironmentsMy: typeof import('./components/environments/my/index.vue')['default']
EnvironmentsMyDetails: (typeof import("./components/environments/my/Details.vue"))["default"] EnvironmentsMyDetails: typeof import('./components/environments/my/Details.vue')['default']
EnvironmentsMyEnvironment: (typeof import("./components/environments/my/Environment.vue"))["default"] EnvironmentsMyEnvironment: typeof import('./components/environments/my/Environment.vue')['default']
EnvironmentsSelector: (typeof import("./components/environments/Selector.vue"))["default"] EnvironmentsSelector: typeof import('./components/environments/Selector.vue')['default']
EnvironmentsTeams: (typeof import("./components/environments/teams/index.vue"))["default"] EnvironmentsTeams: typeof import('./components/environments/teams/index.vue')['default']
EnvironmentsTeamsDetails: (typeof import("./components/environments/teams/Details.vue"))["default"] EnvironmentsTeamsDetails: typeof import('./components/environments/teams/Details.vue')['default']
EnvironmentsTeamsEnvironment: (typeof import("./components/environments/teams/Environment.vue"))["default"] EnvironmentsTeamsEnvironment: typeof import('./components/environments/teams/Environment.vue')['default']
FirebaseLogin: (typeof import("./components/firebase/Login.vue"))["default"] FirebaseLogin: typeof import('./components/firebase/Login.vue')['default']
FirebaseLogout: (typeof import("./components/firebase/Logout.vue"))["default"] FirebaseLogout: typeof import('./components/firebase/Logout.vue')['default']
GraphqlAuthorization: (typeof import("./components/graphql/Authorization.vue"))["default"] GraphqlAuthorization: typeof import('./components/graphql/Authorization.vue')['default']
GraphqlField: (typeof import("./components/graphql/Field.vue"))["default"] GraphqlField: typeof import('./components/graphql/Field.vue')['default']
GraphqlHeaders: (typeof import("./components/graphql/Headers.vue"))["default"] GraphqlHeaders: typeof import('./components/graphql/Headers.vue')['default']
GraphqlQuery: (typeof import("./components/graphql/Query.vue"))["default"] GraphqlQuery: typeof import('./components/graphql/Query.vue')['default']
GraphqlRequest: (typeof import("./components/graphql/Request.vue"))["default"] GraphqlRequest: typeof import('./components/graphql/Request.vue')['default']
GraphqlRequestOptions: (typeof import("./components/graphql/RequestOptions.vue"))["default"] GraphqlRequestOptions: typeof import('./components/graphql/RequestOptions.vue')['default']
GraphqlRequestTab: (typeof import("./components/graphql/RequestTab.vue"))["default"] GraphqlRequestTab: typeof import('./components/graphql/RequestTab.vue')['default']
GraphqlResponse: (typeof import("./components/graphql/Response.vue"))["default"] GraphqlResponse: typeof import('./components/graphql/Response.vue')['default']
GraphqlSidebar: (typeof import("./components/graphql/Sidebar.vue"))["default"] GraphqlSidebar: typeof import('./components/graphql/Sidebar.vue')['default']
GraphqlSubscriptionLog: (typeof import("./components/graphql/SubscriptionLog.vue"))["default"] GraphqlSubscriptionLog: typeof import('./components/graphql/SubscriptionLog.vue')['default']
GraphqlTabHead: (typeof import("./components/graphql/TabHead.vue"))["default"] GraphqlTabHead: typeof import('./components/graphql/TabHead.vue')['default']
GraphqlType: (typeof import("./components/graphql/Type.vue"))["default"] GraphqlType: typeof import('./components/graphql/Type.vue')['default']
GraphqlTypeLink: (typeof import("./components/graphql/TypeLink.vue"))["default"] GraphqlTypeLink: typeof import('./components/graphql/TypeLink.vue')['default']
GraphqlVariable: (typeof import("./components/graphql/Variable.vue"))["default"] GraphqlVariable: typeof import('./components/graphql/Variable.vue')['default']
History: (typeof import("./components/history/index.vue"))["default"] History: typeof import('./components/history/index.vue')['default']
HistoryGraphqlCard: (typeof import("./components/history/graphql/Card.vue"))["default"] HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
HistoryRestCard: (typeof import("./components/history/rest/Card.vue"))["default"] HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
HoppButtonPrimary: (typeof import("@hoppscotch/ui"))["HoppButtonPrimary"] HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: (typeof import("@hoppscotch/ui"))["HoppButtonSecondary"] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: (typeof import("@hoppscotch/ui"))["HoppSmartAnchor"] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartCheckbox: (typeof import("@hoppscotch/ui"))["HoppSmartCheckbox"] HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
HoppSmartConfirmModal: (typeof import("@hoppscotch/ui"))["HoppSmartConfirmModal"] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartExpand: (typeof import("@hoppscotch/ui"))["HoppSmartExpand"] HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
HoppSmartFileChip: (typeof import("@hoppscotch/ui"))["HoppSmartFileChip"] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
HoppSmartInput: (typeof import("@hoppscotch/ui"))["HoppSmartInput"] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartIntersection: (typeof import("@hoppscotch/ui"))["HoppSmartIntersection"] HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
HoppSmartItem: (typeof import("@hoppscotch/ui"))["HoppSmartItem"] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: (typeof import("@hoppscotch/ui"))["HoppSmartLink"] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: (typeof import("@hoppscotch/ui"))["HoppSmartModal"] HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
HoppSmartPicture: (typeof import("@hoppscotch/ui"))["HoppSmartPicture"] HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartPlaceholder: (typeof import("@hoppscotch/ui"))["HoppSmartPlaceholder"] HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
HoppSmartProgressRing: (typeof import("@hoppscotch/ui"))["HoppSmartProgressRing"] HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
HoppSmartRadio: (typeof import("@hoppscotch/ui"))["HoppSmartRadio"] HoppSmartRadio: typeof import('@hoppscotch/ui')['HoppSmartRadio']
HoppSmartRadioGroup: (typeof import("@hoppscotch/ui"))["HoppSmartRadioGroup"] HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
HoppSmartSelectWrapper: (typeof import("@hoppscotch/ui"))["HoppSmartSelectWrapper"] HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper']
HoppSmartSlideOver: (typeof import("@hoppscotch/ui"))["HoppSmartSlideOver"] HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
HoppSmartSpinner: (typeof import("@hoppscotch/ui"))["HoppSmartSpinner"] HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
HoppSmartTab: (typeof import("@hoppscotch/ui"))["HoppSmartTab"] HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
HoppSmartTabs: (typeof import("@hoppscotch/ui"))["HoppSmartTabs"] HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
HoppSmartToggle: (typeof import("@hoppscotch/ui"))["HoppSmartToggle"] HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
HoppSmartTree: (typeof import("@hoppscotch/ui"))["HoppSmartTree"] HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree']
HoppSmartWindow: (typeof import("@hoppscotch/ui"))["HoppSmartWindow"] HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
HoppSmartWindows: (typeof import("@hoppscotch/ui"))["HoppSmartWindows"] HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
HttpAuthorization: (typeof import("./components/http/Authorization.vue"))["default"] HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
HttpAuthorizationApiKey: (typeof import("./components/http/authorization/ApiKey.vue"))["default"] HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
HttpAuthorizationBasic: (typeof import("./components/http/authorization/Basic.vue"))["default"] HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
HttpBody: (typeof import("./components/http/Body.vue"))["default"] HttpBody: typeof import('./components/http/Body.vue')['default']
HttpBodyParameters: (typeof import("./components/http/BodyParameters.vue"))["default"] HttpBodyParameters: typeof import('./components/http/BodyParameters.vue')['default']
HttpCodegenModal: (typeof import("./components/http/CodegenModal.vue"))["default"] HttpCodegenModal: typeof import('./components/http/CodegenModal.vue')['default']
HttpHeaders: (typeof import("./components/http/Headers.vue"))["default"] HttpHeaders: typeof import('./components/http/Headers.vue')['default']
HttpImportCurl: (typeof import("./components/http/ImportCurl.vue"))["default"] HttpImportCurl: typeof import('./components/http/ImportCurl.vue')['default']
HttpOAuth2Authorization: (typeof import("./components/http/OAuth2Authorization.vue"))["default"] HttpOAuth2Authorization: typeof import('./components/http/OAuth2Authorization.vue')['default']
HttpParameters: (typeof import("./components/http/Parameters.vue"))["default"] HttpParameters: typeof import('./components/http/Parameters.vue')['default']
HttpPreRequestScript: (typeof import("./components/http/PreRequestScript.vue"))["default"] HttpPreRequestScript: typeof import('./components/http/PreRequestScript.vue')['default']
HttpRawBody: (typeof import("./components/http/RawBody.vue"))["default"] HttpRawBody: typeof import('./components/http/RawBody.vue')['default']
HttpReqChangeConfirmModal: (typeof import("./components/http/ReqChangeConfirmModal.vue"))["default"] HttpReqChangeConfirmModal: typeof import('./components/http/ReqChangeConfirmModal.vue')['default']
HttpRequest: (typeof import("./components/http/Request.vue"))["default"] HttpRequest: typeof import('./components/http/Request.vue')['default']
HttpRequestOptions: (typeof import("./components/http/RequestOptions.vue"))["default"] HttpRequestOptions: typeof import('./components/http/RequestOptions.vue')['default']
HttpRequestTab: (typeof import("./components/http/RequestTab.vue"))["default"] HttpRequestTab: typeof import('./components/http/RequestTab.vue')['default']
HttpResponse: (typeof import("./components/http/Response.vue"))["default"] HttpResponse: typeof import('./components/http/Response.vue')['default']
HttpResponseMeta: (typeof import("./components/http/ResponseMeta.vue"))["default"] HttpResponseMeta: typeof import('./components/http/ResponseMeta.vue')['default']
HttpSidebar: (typeof import("./components/http/Sidebar.vue"))["default"] HttpSidebar: typeof import('./components/http/Sidebar.vue')['default']
HttpTabHead: (typeof import("./components/http/TabHead.vue"))["default"] HttpTabHead: typeof import('./components/http/TabHead.vue')['default']
HttpTestResult: (typeof import("./components/http/TestResult.vue"))["default"] HttpTestResult: typeof import('./components/http/TestResult.vue')['default']
HttpTestResultEntry: (typeof import("./components/http/TestResultEntry.vue"))["default"] HttpTestResultEntry: typeof import('./components/http/TestResultEntry.vue')['default']
HttpTestResultEnv: (typeof import("./components/http/TestResultEnv.vue"))["default"] HttpTestResultEnv: typeof import('./components/http/TestResultEnv.vue')['default']
HttpTestResultReport: (typeof import("./components/http/TestResultReport.vue"))["default"] HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
HttpTests: (typeof import("./components/http/Tests.vue"))["default"] HttpTests: typeof import('./components/http/Tests.vue')['default']
HttpURLEncodedParams: (typeof import("./components/http/URLEncodedParams.vue"))["default"] HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
IconLucideActivity: (typeof import("~icons/lucide/activity"))["default"] IconLucideActivity: typeof import('~icons/lucide/activity')['default']
IconLucideAlertTriangle: (typeof import("~icons/lucide/alert-triangle"))["default"] IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: (typeof import("~icons/lucide/arrow-left"))["default"] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: (typeof import("~icons/lucide/arrow-up-right"))["default"] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideCheckCircle: (typeof import("~icons/lucide/check-circle"))["default"] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideChevronRight: (typeof import("~icons/lucide/chevron-right"))["default"] IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
IconLucideGlobe: (typeof import("~icons/lucide/globe"))["default"] IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
IconLucideHelpCircle: (typeof import("~icons/lucide/help-circle"))["default"] IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideInbox: (typeof import("~icons/lucide/inbox"))["default"] IconLucideInfo: typeof import('~icons/lucide/info')['default']
IconLucideInfo: (typeof import("~icons/lucide/info"))["default"] IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideLayers: (typeof import("~icons/lucide/layers"))["default"] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideListEnd: (typeof import("~icons/lucide/list-end"))["default"] IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideMinus: (typeof import("~icons/lucide/minus"))["default"] IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"] IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideSearch: (typeof import("~icons/lucide/search"))["default"] IconLucideX: typeof import('~icons/lucide/x')['default']
IconLucideUsers: (typeof import("~icons/lucide/users"))["default"] ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
IconLucideX: (typeof import("~icons/lucide/x"))["default"] ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
ImportExportBase: (typeof import("./components/importExport/Base.vue"))["default"] ImportExportImportExportSourcesList: typeof import('./components/importExport/ImportExportSourcesList.vue')['default']
ImportExportImportExportList: (typeof import("./components/importExport/ImportExportList.vue"))["default"] ImportExportImportExportStepsFileImport: typeof import('./components/importExport/ImportExportSteps/FileImport.vue')['default']
ImportExportImportExportSourcesList: (typeof import("./components/importExport/ImportExportSourcesList.vue"))["default"] ImportExportImportExportStepsMyCollectionImport: typeof import('./components/importExport/ImportExportSteps/MyCollectionImport.vue')['default']
ImportExportImportExportStepsFileImport: (typeof import("./components/importExport/ImportExportSteps/FileImport.vue"))["default"] ImportExportImportExportStepsUrlImport: typeof import('./components/importExport/ImportExportSteps/UrlImport.vue')['default']
ImportExportImportExportStepsMyCollectionImport: (typeof import("./components/importExport/ImportExportSteps/MyCollectionImport.vue"))["default"] InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
ImportExportImportExportStepsUrlImport: (typeof import("./components/importExport/ImportExportSteps/UrlImport.vue"))["default"] InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
InterceptorsErrorPlaceholder: (typeof import("./components/interceptors/ErrorPlaceholder.vue"))["default"] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
InterceptorsExtensionSubtitle: (typeof import("./components/interceptors/ExtensionSubtitle.vue"))["default"] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
LensesHeadersRenderer: (typeof import("./components/lenses/HeadersRenderer.vue"))["default"] LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
LensesHeadersRendererEntry: (typeof import("./components/lenses/HeadersRendererEntry.vue"))["default"] LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
LensesRenderersAudioLensRenderer: (typeof import("./components/lenses/renderers/AudioLensRenderer.vue"))["default"] LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
LensesRenderersHTMLLensRenderer: (typeof import("./components/lenses/renderers/HTMLLensRenderer.vue"))["default"] LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
LensesRenderersImageLensRenderer: (typeof import("./components/lenses/renderers/ImageLensRenderer.vue"))["default"] LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
LensesRenderersJSONLensRenderer: (typeof import("./components/lenses/renderers/JSONLensRenderer.vue"))["default"] LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
LensesRenderersPDFLensRenderer: (typeof import("./components/lenses/renderers/PDFLensRenderer.vue"))["default"] LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
LensesRenderersRawLensRenderer: (typeof import("./components/lenses/renderers/RawLensRenderer.vue"))["default"] LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
LensesRenderersVideoLensRenderer: (typeof import("./components/lenses/renderers/VideoLensRenderer.vue"))["default"] LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
LensesRenderersXMLLensRenderer: (typeof import("./components/lenses/renderers/XMLLensRenderer.vue"))["default"] ProfileUserDelete: typeof import('./components/profile/UserDelete.vue')['default']
LensesResponseBodyRenderer: (typeof import("./components/lenses/ResponseBodyRenderer.vue"))["default"] RealtimeCommunication: typeof import('./components/realtime/Communication.vue')['default']
ProfileUserDelete: (typeof import("./components/profile/UserDelete.vue"))["default"] RealtimeConnectionConfig: typeof import('./components/realtime/ConnectionConfig.vue')['default']
RealtimeCommunication: (typeof import("./components/realtime/Communication.vue"))["default"] RealtimeLog: typeof import('./components/realtime/Log.vue')['default']
RealtimeConnectionConfig: (typeof import("./components/realtime/ConnectionConfig.vue"))["default"] RealtimeLogEntry: typeof import('./components/realtime/LogEntry.vue')['default']
RealtimeLog: (typeof import("./components/realtime/Log.vue"))["default"] RealtimeSubscription: typeof import('./components/realtime/Subscription.vue')['default']
RealtimeLogEntry: (typeof import("./components/realtime/LogEntry.vue"))["default"] SettingsExtension: typeof import('./components/settings/Extension.vue')['default']
RealtimeSubscription: (typeof import("./components/realtime/Subscription.vue"))["default"] SettingsProxy: typeof import('./components/settings/Proxy.vue')['default']
SettingsExtension: (typeof import("./components/settings/Extension.vue"))["default"] Share: typeof import('./components/share/index.vue')['default']
SettingsProxy: (typeof import("./components/settings/Proxy.vue"))["default"] ShareCreateModal: typeof import('./components/share/CreateModal.vue')['default']
Share: (typeof import("./components/share/index.vue"))["default"] ShareCustomizeModal: typeof import('./components/share/CustomizeModal.vue')['default']
ShareCreateModal: (typeof import("./components/share/CreateModal.vue"))["default"] ShareModal: typeof import('./components/share/Modal.vue')['default']
ShareCustomizeModal: (typeof import("./components/share/CustomizeModal.vue"))["default"] ShareRequest: typeof import('./components/share/Request.vue')['default']
ShareModal: (typeof import("./components/share/Modal.vue"))["default"] ShareTemplatesButton: typeof import('./components/share/templates/Button.vue')['default']
ShareRequest: (typeof import("./components/share/Request.vue"))["default"] ShareTemplatesEmbeds: typeof import('./components/share/templates/Embeds.vue')['default']
ShareTemplatesButton: (typeof import("./components/share/templates/Button.vue"))["default"] ShareTemplatesLink: typeof import('./components/share/templates/Link.vue')['default']
ShareTemplatesEmbeds: (typeof import("./components/share/templates/Embeds.vue"))["default"] SmartAccentModePicker: typeof import('./components/smart/AccentModePicker.vue')['default']
ShareTemplatesLink: (typeof import("./components/share/templates/Link.vue"))["default"] SmartChangeLanguage: typeof import('./components/smart/ChangeLanguage.vue')['default']
SmartAccentModePicker: (typeof import("./components/smart/AccentModePicker.vue"))["default"] SmartColorModePicker: typeof import('./components/smart/ColorModePicker.vue')['default']
SmartChangeLanguage: (typeof import("./components/smart/ChangeLanguage.vue"))["default"] SmartEnvInput: typeof import('./components/smart/EnvInput.vue')['default']
SmartColorModePicker: (typeof import("./components/smart/ColorModePicker.vue"))["default"] TabPrimary: typeof import('./components/tab/Primary.vue')['default']
SmartEnvInput: (typeof import("./components/smart/EnvInput.vue"))["default"] TabSecondary: typeof import('./components/tab/Secondary.vue')['default']
TabPrimary: (typeof import("./components/tab/Primary.vue"))["default"] Teams: typeof import('./components/teams/index.vue')['default']
TabSecondary: (typeof import("./components/tab/Secondary.vue"))["default"] TeamsAdd: typeof import('./components/teams/Add.vue')['default']
Teams: (typeof import("./components/teams/index.vue"))["default"] TeamsEdit: typeof import('./components/teams/Edit.vue')['default']
TeamsAdd: (typeof import("./components/teams/Add.vue"))["default"] TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
TeamsEdit: (typeof import("./components/teams/Edit.vue"))["default"] TeamsMemberStack: typeof import('./components/teams/MemberStack.vue')['default']
TeamsInvite: (typeof import("./components/teams/Invite.vue"))["default"] TeamsModal: typeof import('./components/teams/Modal.vue')['default']
TeamsMemberStack: (typeof import("./components/teams/MemberStack.vue"))["default"] TeamsTeam: typeof import('./components/teams/Team.vue')['default']
TeamsModal: (typeof import("./components/teams/Modal.vue"))["default"] Tippy: typeof import('vue-tippy')['Tippy']
TeamsTeam: (typeof import("./components/teams/Team.vue"))["default"] WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
Tippy: (typeof import("vue-tippy"))["Tippy"] WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
WorkspaceCurrent: (typeof import("./components/workspace/Current.vue"))["default"]
WorkspaceSelector: (typeof import("./components/workspace/Selector.vue"))["default"]
} }
} }

View File

@@ -1,8 +1,8 @@
<template> <template>
<div <div
ref="contextMenuRef" ref="contextMenuRef"
class="fixed transform -translate-x-10 -translate-y-8 rounded border border-dividerDark bg-popover p-2 shadow-lg" class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg"
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 100;`" :style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`"
> >
<div v-if="contextMenuOptions" class="flex flex-col"> <div v-if="contextMenuOptions" class="flex flex-col">
<div <div

View File

@@ -330,11 +330,11 @@ const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
const workspace = workspaceService.currentWorkspace const workspace = workspaceService.currentWorkspace
const workspaceName = computed(() => { const workspaceName = computed(() =>
return workspace.value.type === "personal" workspace.value.type === "personal"
? t("workspace.personal") ? t("workspace.personal")
: workspace.value.teamName : workspace.value.teamName
}) )
const refetchTeams = () => { const refetchTeams = () => {
teamListAdapter.fetchList() teamListAdapter.fetchList()

View File

@@ -165,7 +165,7 @@ import { environmentsStore } from "~/newstore/environments"
import { platform } from "~/platform" import { platform } from "~/platform"
import { useService } from "dioc/vue" import { useService } from "dioc/vue"
import { SecretEnvironmentService } from "~/services/secret-environment.service" import { SecretEnvironmentService } from "~/services/secret-environment.service"
import { uniqueID } from "~/helpers/utils/uniqueID" import { uniqueId } from "lodash-es"
type EnvironmentVariable = { type EnvironmentVariable = {
id: number id: number
@@ -277,7 +277,7 @@ const workingEnv = computed(() => {
} as Environment } as Environment
} else if (props.action === "new") { } else if (props.action === "new") {
return { return {
id: uniqueID(), id: uniqueId(),
name: "", name: "",
variables: props.envVars(), variables: props.envVars(),
} }
@@ -331,7 +331,7 @@ watch(
: "variables" : "variables"
if (props.editingEnvironmentIndex !== "Global") { if (props.editingEnvironmentIndex !== "Global") {
editingID.value = workingEnv.value?.id || uniqueID() editingID.value = workingEnv.value?.id ?? uniqueId()
} }
vars.value = pipe( vars.value = pipe(
workingEnv.value?.variables ?? [], workingEnv.value?.variables ?? [],
@@ -416,12 +416,14 @@ const saveEnvironment = () => {
const variables = pipe( const variables = pipe(
filteredVariables, filteredVariables,
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e)) A.map((e) =>
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
)
) )
const environmentUpdated: Environment = { const environmentUpdated: Environment = {
v: 1, v: 1,
id: uniqueID(), id: uniqueId(),
name: editingName.value, name: editingName.value,
variables, variables,
} }

View File

@@ -360,7 +360,7 @@ const saveEnvironment = async () => {
return return
} }
const filteredVariables = pipe( const filterdVariables = pipe(
vars.value, vars.value,
A.filterMap( A.filterMap(
flow( flow(
@@ -371,15 +371,17 @@ const saveEnvironment = async () => {
) )
const secretVariables = pipe( const secretVariables = pipe(
filteredVariables, filterdVariables,
A.filterMapWithIndex((i, e) => A.filterMapWithIndex((i, e) =>
e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none e.secret ? O.some({ key: e.key, value: e.value, varIndex: i }) : O.none
) )
) )
const variables = pipe( const variables = pipe(
filteredVariables, filterdVariables,
A.map((e) => (e.secret ? { key: e.key, secret: e.secret } : e)) A.map((e) =>
e.secret ? { key: e.key, secret: e.secret, value: undefined } : e
)
) )
const environmentUpdated: Environment = { const environmentUpdated: Environment = {

View File

@@ -417,9 +417,7 @@ function handleTextSelection() {
const { from, to } = selection const { from, to } = selection
if (from === to) return if (from === to) return
const text = view.value?.state.doc.sliceString(from, to) const text = view.value?.state.doc.sliceString(from, to)
const coords = view.value?.coordsAtPos(from) const { top, left } = view.value?.coordsAtPos(from)
const top = coords?.top ?? 0
const left = coords?.left ?? 0
if (text) { if (text) {
invokeAction("contextmenu.open", { invokeAction("contextmenu.open", {
position: { position: {
@@ -441,17 +439,16 @@ function handleTextSelection() {
} }
} }
// Debounce to prevent double click from selecting the word
const debouncedTextSelection = (time: number) =>
useDebounceFn(() => {
handleTextSelection()
}, time)
const initView = (el: any) => { const initView = (el: any) => {
// Debounce to prevent double click from selecting the word
const debounceFn = useDebounceFn(() => {
handleTextSelection()
}, 140)
// Only add event listeners if context menu is enabled in the component // Only add event listeners if context menu is enabled in the component
if (props.contextMenuEnabled) { if (props.contextMenuEnabled) {
el.addEventListener("mouseup", debouncedTextSelection(140)) el.addEventListener("mouseup", debounceFn)
el.addEventListener("keyup", debouncedTextSelection(140)) el.addEventListener("keyup", debounceFn)
} }
const extensions: Extension = getExtensions(props.readonly || isSecret.value) const extensions: Extension = getExtensions(props.readonly || isSecret.value)
@@ -501,8 +498,7 @@ const getExtensions = (readonly: boolean): Extension => {
}, },
scroll(event) { scroll(event) {
if (event.target && props.contextMenuEnabled) { if (event.target && props.contextMenuEnabled) {
// Debounce to make the performance better handleTextSelection()
debouncedTextSelection(30)()
} }
}, },
}), }),

View File

@@ -27,10 +27,13 @@ const workspaceService = useService(WorkspaceService)
const workspace = workspaceService.currentWorkspace const workspace = workspaceService.currentWorkspace
const currentWorkspace = computed(() => { const currentWorkspace = computed(() => {
if (props.isOnlyPersonal || workspace.value.type === "personal") { if (props.isOnlyPersonal) {
return t("workspace.personal") return `${t("workspace.personal")}`
} }
return teamWorkspaceName.value if (workspace.value.type === "team") {
return teamWorkspaceName.value
}
return `${t("workspace.personal")}`
}) })
const teamWorkspaceName = computed(() => { const teamWorkspaceName = computed(() => {

View File

@@ -3,7 +3,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex flex-col"> <div class="flex flex-col">
<HoppSmartItem <HoppSmartItem
:label="t('workspace.personal')" label="My Workspace"
:icon="IconUser" :icon="IconUser"
:info-icon="workspace.type === 'personal' ? IconDone : undefined" :info-icon="workspace.type === 'personal' ? IconDone : undefined"
:active-info-icon="workspace.type === 'personal'" :active-info-icon="workspace.type === 'personal'"
@@ -36,7 +36,7 @@
class="sticky top-0 z-10 mb-2 flex items-center justify-between bg-popover py-2 pl-2" class="sticky top-0 z-10 mb-2 flex items-center justify-between bg-popover py-2 pl-2"
> >
<div class="flex items-center px-2 font-semibold text-secondaryLight"> <div class="flex items-center px-2 font-semibold text-secondaryLight">
{{ t("workspace.other_workspaces") }} {{ t("team.title") }}
</div> </div>
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"

View File

@@ -242,9 +242,7 @@ export function useCodemirror(
const { from, to } = selection const { from, to } = selection
if (from === to) return if (from === to) return
const text = view.value?.state.doc.sliceString(from, to) const text = view.value?.state.doc.sliceString(from, to)
const coords = view.value?.coordsAtPos(from) const { top, left } = view.value?.coordsAtPos(from)
const top = coords?.top ?? 0
const left = coords?.left ?? 0
if (text?.trim()) { if (text?.trim()) {
invokeAction("contextmenu.open", { invokeAction("contextmenu.open", {
position: { position: {
@@ -265,12 +263,6 @@ export function useCodemirror(
} }
} }
// Debounce to prevent double click from selecting the word
const debouncedTextSelection = (time: number) =>
useDebounceFn(() => {
handleTextSelection()
}, time)
const initView = (el: any) => { const initView = (el: any) => {
if (el) platform.ui?.onCodemirrorInstanceMount?.(el) if (el) platform.ui?.onCodemirrorInstanceMount?.(el)
@@ -282,10 +274,15 @@ export function useCodemirror(
ViewPlugin.fromClass( ViewPlugin.fromClass(
class { class {
update(update: ViewUpdate) { update(update: ViewUpdate) {
// Debounce to prevent double click from selecting the word
const debounceFn = useDebounceFn(() => {
handleTextSelection()
}, 140)
// Only add event listeners if context menu is enabled in the editor // Only add event listeners if context menu is enabled in the editor
if (options.contextMenuEnabled) { if (options.contextMenuEnabled) {
el.addEventListener("mouseup", debouncedTextSelection(140)) el.addEventListener("mouseup", debounceFn)
el.addEventListener("keyup", debouncedTextSelection(140)) el.addEventListener("keyup", debounceFn)
} }
if (options.onUpdate) { if (options.onUpdate) {
@@ -327,8 +324,7 @@ export function useCodemirror(
EditorView.domEventHandlers({ EditorView.domEventHandlers({
scroll(event) { scroll(event) {
if (event.target && options.contextMenuEnabled) { if (event.target && options.contextMenuEnabled) {
// Debounce to make the performance better handleTextSelection()
debouncedTextSelection(30)()
} }
}, },
}), }),

View File

@@ -4,12 +4,11 @@ import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray" import * as RA from "fp-ts/ReadonlyArray"
import * as A from "fp-ts/Array" import * as A from "fp-ts/Array"
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data" import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
import { isPlainObject as _isPlainObject } from "lodash-es"
import { IMPORTER_INVALID_FILE_FORMAT } from "." import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { safeParseJSON } from "~/helpers/functional/json" import { safeParseJSON } from "~/helpers/functional/json"
import { translateToNewGQLCollection } from "@hoppscotch/data" import { translateToNewGQLCollection } from "@hoppscotch/data"
import { entityReference } from "verzod"
import { z } from "zod"
export const hoppRESTImporter = (content: string[]) => export const hoppRESTImporter = (content: string[]) =>
pipe( pipe(
@@ -27,14 +26,26 @@ export const hoppRESTImporter = (content: string[]) =>
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT) TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
) )
/**
* checks if a value is a plain object
*/
const isPlainObject = (value: any): value is object => _isPlainObject(value)
/**
* checks if a collection matches the schema for a hoppscotch collection.
* here 2 is the latest version of the schema.
*/
const isValidCollection = (collection: unknown): collection is HoppCollection =>
isPlainObject(collection) && "v" in collection && collection.v === 2
/** /**
* checks if a collection is a valid hoppscotch collection. * checks if a collection is a valid hoppscotch collection.
* else translate it into one. * else translate it into one.
*/ */
const validateCollection = (collection: unknown) => { const validateCollection = (collection: unknown) => {
const result = entityReference(HoppCollection).safeParse(collection) if (isValidCollection(collection)) {
if (result.success) return O.some(result.data) return O.some(collection)
}
return O.some(translateToNewRESTCollection(collection)) return O.some(translateToNewRESTCollection(collection))
} }
@@ -64,9 +75,8 @@ export const hoppGQLImporter = (content: string) =>
* @returns the collection if it is valid, else a translated version of the collection * @returns the collection if it is valid, else a translated version of the collection
*/ */
export const validateGQLCollection = (collection: unknown) => { export const validateGQLCollection = (collection: unknown) => {
const result = z.array(entityReference(HoppCollection)).safeParse(collection) if (isValidCollection(collection)) {
return O.some(collection)
if (result.success) return O.some(result.data) }
return O.some(translateToNewGQLCollection(collection)) return O.some(translateToNewGQLCollection(collection))
} }

View File

@@ -6,7 +6,7 @@ import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { z } from "zod" import { z } from "zod"
import { NonSecretEnvironment } from "@hoppscotch/data" import { NonSecretEnvironment } from "@hoppscotch/data"
import { safeParseJSONOrYAML } from "~/helpers/functional/yaml" import { safeParseJSONOrYAML } from "~/helpers/functional/yaml"
import { uniqueID } from "~/helpers/utils/uniqueID" import { uniqueId } from "lodash-es"
const insomniaResourcesSchema = z.object({ const insomniaResourcesSchema = z.object({
resources: z.array( resources: z.array(
@@ -67,7 +67,7 @@ export const insomniaEnvImporter = (contents: string[]) => {
if (parsedInsomniaEnv.success) { if (parsedInsomniaEnv.success) {
const environment: NonSecretEnvironment = { const environment: NonSecretEnvironment = {
id: uniqueID(), id: uniqueId(),
v: 1, v: 1,
name: parsedInsomniaEnv.data.name, name: parsedInsomniaEnv.data.name,
variables: Object.entries(parsedInsomniaEnv.data.data).map( variables: Object.entries(parsedInsomniaEnv.data.data).map(

View File

@@ -17,7 +17,6 @@ import {
HoppCollection, HoppCollection,
makeCollection, makeCollection,
HoppRESTRequestVariable, HoppRESTRequestVariable,
HoppRESTRequest,
} from "@hoppscotch/data" } from "@hoppscotch/data"
import { pipe, flow } from "fp-ts/function" import { pipe, flow } from "fp-ts/function"
import * as A from "fp-ts/Array" import * as A from "fp-ts/Array"
@@ -26,7 +25,6 @@ import * as O from "fp-ts/Option"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import * as RA from "fp-ts/ReadonlyArray" import * as RA from "fp-ts/ReadonlyArray"
import { IMPORTER_INVALID_FILE_FORMAT } from "." import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { cloneDeep } from "lodash-es"
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
@@ -582,42 +580,30 @@ const convertPathToHoppReqs = (
? openAPIUrl + openAPIPath.slice(1) ? openAPIUrl + openAPIPath.slice(1)
: openAPIUrl + openAPIPath : openAPIUrl + openAPIPath
const res: { return makeRESTRequest({
request: HoppRESTRequest name: info.operationId ?? info.summary ?? "Untitled Request",
metadata: { method: method.toUpperCase(),
tags: string[] endpoint,
}
} = {
request: makeRESTRequest({
name: info.operationId ?? info.summary ?? "Untitled Request",
method: method.toUpperCase(),
endpoint,
// We don't need to worry about reference types as the Dereferencing pass should remove them // We don't need to worry about reference types as the Dereferencing pass should remove them
params: parseOpenAPIParams( params: parseOpenAPIParams(
(info.parameters as OpenAPIParamsType[] | undefined) ?? [] (info.parameters as OpenAPIParamsType[] | undefined) ?? []
), ),
headers: parseOpenAPIHeaders( headers: parseOpenAPIHeaders(
(info.parameters as OpenAPIParamsType[] | undefined) ?? [] (info.parameters as OpenAPIParamsType[] | undefined) ?? []
), ),
auth: parseOpenAPIAuth(doc, info), auth: parseOpenAPIAuth(doc, info),
body: parseOpenAPIBody(doc, info), body: parseOpenAPIBody(doc, info),
preRequestScript: "", preRequestScript: "",
testScript: "", testScript: "",
requestVariables: parseOpenAPIVariables( requestVariables: parseOpenAPIVariables(
(info.parameters as OpenAPIParamsType[] | undefined) ?? [] (info.parameters as OpenAPIParamsType[] | undefined) ?? []
), ),
}), })
metadata: {
tags: info.tags ?? [],
},
}
return res
}), }),
// Disable Readonly // Disable Readonly
@@ -636,38 +622,10 @@ const convertOpenApiDocsToHopp = (
) )
.flat() .flat()
const requestsByTags: Record<string, Array<HoppRESTRequest>> = {}
const requestsWithoutTags: Array<HoppRESTRequest> = []
paths.forEach(({ metadata, request }) => {
const tags = metadata.tags
if (tags.length === 0) {
requestsWithoutTags.push(request)
return
}
for (const tag of tags) {
if (!requestsByTags[tag]) {
requestsByTags[tag] = []
}
requestsByTags[tag].push(cloneDeep(request))
}
})
return makeCollection({ return makeCollection({
name, name,
folders: Object.entries(requestsByTags).map(([name, paths]) => folders: [],
makeCollection({ requests: paths,
name,
requests: paths,
folders: [],
auth: { authType: "inherit", authActive: true },
headers: [],
})
),
requests: requestsWithoutTags,
auth: { authType: "inherit", authActive: true }, auth: { authType: "inherit", authActive: true },
headers: [], headers: [],
}) })

View File

@@ -1,11 +1,11 @@
import { Environment } from "@hoppscotch/data" import { Environment } from "@hoppscotch/data"
import * as O from "fp-ts/Option" import * as O from "fp-ts/Option"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { uniqueId } from "lodash-es"
import { z } from "zod" import { z } from "zod"
import { safeParseJSON } from "~/helpers/functional/json" import { safeParseJSON } from "~/helpers/functional/json"
import { IMPORTER_INVALID_FILE_FORMAT } from "." import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { uniqueID } from "~/helpers/utils/uniqueID"
const postmanEnvSchema = z.object({ const postmanEnvSchema = z.object({
name: z.string(), name: z.string(),
@@ -49,7 +49,7 @@ export const postmanEnvImporter = (contents: string[]) => {
// Convert `values` to `variables` to match the format expected by the system // Convert `values` to `variables` to match the format expected by the system
const environments: Environment[] = validationResult.data.map( const environments: Environment[] = validationResult.data.map(
({ name, values }) => ({ ({ name, values }) => ({
id: uniqueID(), id: uniqueId(),
v: 1, v: 1,
name, name,
variables: values.map((entires) => ({ ...entires, secret: false })), variables: values.map((entires) => ({ ...entires, secret: false })),

View File

@@ -1,3 +0,0 @@
export const uniqueID = (length = 16) => {
return Math.random().toString(36).substring(2, length)
}

View File

@@ -34,7 +34,7 @@ export type HoppModule = {
to: RouteLocationNormalized, to: RouteLocationNormalized,
from: RouteLocationNormalized, from: RouteLocationNormalized,
router: Router router: Router
) => void | Promise<void> ) => void
/** /**
* Called by the router to tell all the modules that a route navigation has completed * Called by the router to tell all the modules that a route navigation has completed

View File

@@ -47,21 +47,15 @@ export default <HoppModule>{
routes, routes,
}) })
router.beforeEach(async (to, from) => { router.beforeEach((to, from) => {
_isLoadingInitialRoute.value = isInitialRoute(from) _isLoadingInitialRoute.value = isInitialRoute(from)
const onBeforeRouteChangePromises: Promise<any>[] = []
HOPP_MODULES.forEach((mod) => { HOPP_MODULES.forEach((mod) => {
const res = mod.onBeforeRouteChange?.(to, from, router) mod.onBeforeRouteChange?.(to, from, router)
if (res) onBeforeRouteChangePromises.push(res)
}) })
platform.addedHoppModules?.forEach((mod) => { platform.addedHoppModules?.forEach((mod) => {
const res = mod.onBeforeRouteChange?.(to, from, router) mod.onBeforeRouteChange?.(to, from, router)
if (res) onBeforeRouteChangePromises.push(res)
}) })
await Promise.all(onBeforeRouteChangePromises)
}) })
// Instead of this a better architecture is for the router // Instead of this a better architecture is for the router

View File

@@ -1,8 +1,7 @@
import { Environment } from "@hoppscotch/data" import { Environment } from "@hoppscotch/data"
import { cloneDeep, isEqual } from "lodash-es" import { cloneDeep, isEqual, uniqueId } from "lodash-es"
import { combineLatest, Observable } from "rxjs" import { combineLatest, Observable } from "rxjs"
import { distinctUntilChanged, map, pluck } from "rxjs/operators" import { distinctUntilChanged, map, pluck } from "rxjs/operators"
import { uniqueID } from "~/helpers/utils/uniqueID"
import { getService } from "~/modules/dioc" import { getService } from "~/modules/dioc"
import DispatchingStore, { import DispatchingStore, {
defineDispatchers, defineDispatchers,
@@ -23,7 +22,7 @@ const defaultEnvironmentsState = {
environments: [ environments: [
{ {
v: 1, v: 1,
id: uniqueID(), id: uniqueId(),
name: "My Environment Variables", name: "My Environment Variables",
variables: [], variables: [],
}, },
@@ -101,7 +100,7 @@ const dispatchers = defineDispatchers({
} }
: { : {
v: 1, v: 1,
id: uniqueID(), id: "",
name, name,
variables, variables,
}, },
@@ -124,7 +123,7 @@ const dispatchers = defineDispatchers({
...environments, ...environments,
{ {
...cloneDeep(newEnvironment), ...cloneDeep(newEnvironment),
id: uniqueID(), id: uniqueId(),
name: `${newEnvironment.name} - Duplicate`, name: `${newEnvironment.name} - Duplicate`,
}, },
], ],

View File

@@ -44,5 +44,4 @@ export default defineComponent({
<route lang="yaml"> <route lang="yaml">
meta: meta:
layout: empty layout: empty
onlyGuest: true
</route> </route>

View File

@@ -71,7 +71,7 @@
<h1 class="heading"> <h1 class="heading">
{{ {{
t("team.join_team", { t("team.join_team", {
workspace: inviteDetails.data.right.teamInvitation.team.name, team: inviteDetails.data.right.teamInvitation.team.name,
}) })
}} }}
</h1> </h1>
@@ -81,7 +81,7 @@
owner: owner:
inviteDetails.data.right.teamInvitation.creator.displayName ?? inviteDetails.data.right.teamInvitation.creator.displayName ??
inviteDetails.data.right.teamInvitation.creator.email, inviteDetails.data.right.teamInvitation.creator.email,
workspace: inviteDetails.data.right.teamInvitation.team.name, team: inviteDetails.data.right.teamInvitation.team.name,
}) })
}} }}
</p> </p>
@@ -89,7 +89,7 @@
<HoppButtonPrimary <HoppButtonPrimary
:label=" :label="
t('team.join_team', { t('team.join_team', {
workspace: inviteDetails.data.right.teamInvitation.team.name, team: inviteDetails.data.right.teamInvitation.team.name,
}) })
" "
:loading="loading" :loading="loading"
@@ -109,14 +109,14 @@
<h1 class="heading"> <h1 class="heading">
{{ {{
t("team.joined_team", { t("team.joined_team", {
workspace: inviteDetails.data.right.teamInvitation.team.name, team: inviteDetails.data.right.teamInvitation.team.name,
}) })
}} }}
</h1> </h1>
<p class="mt-2 text-secondaryLight"> <p class="mt-2 text-secondaryLight">
{{ {{
t("team.joined_team_description", { t("team.joined_team_description", {
workspace: inviteDetails.data.right.teamInvitation.team.name, team: inviteDetails.data.right.teamInvitation.team.name,
}) })
}} }}
</p> </p>

View File

@@ -34,16 +34,16 @@
}, },
"homepage": "https://github.com/hoppscotch/hoppscotch#readme", "homepage": "https://github.com/hoppscotch/hoppscotch#readme",
"devDependencies": { "devDependencies": {
"@types/lodash": "4.14.200", "@types/lodash": "^4.14.200",
"typescript": "5.2.2", "typescript": "^5.2.2",
"vite": "4.5.0" "vite": "^4.5.0"
}, },
"dependencies": { "dependencies": {
"fp-ts": "2.16.1", "fp-ts": "^2.16.1",
"io-ts": "2.2.20", "io-ts": "^2.2.20",
"lodash": "4.17.21", "lodash": "^4.17.21",
"parser-ts": "0.7.0", "parser-ts": "^0.7.0",
"verzod": "0.2.2", "verzod": "^0.2.2",
"zod": "3.22.4" "zod": "^3.22.4"
} }
} }

View File

@@ -52,6 +52,8 @@ export function makeCollection(x: Omit<HoppCollection, "v">): HoppCollection {
* @returns The proper new collection format * @returns The proper new collection format
*/ */
export function translateToNewRESTCollection(x: any): HoppCollection { export function translateToNewRESTCollection(x: any): HoppCollection {
if (x.v && x.v === CollectionSchemaVersion) return x
// Legacy // Legacy
const name = x.name ?? "Untitled" const name = x.name ?? "Untitled"
const folders = (x.folders ?? []).map(translateToNewRESTCollection) const folders = (x.folders ?? []).map(translateToNewRESTCollection)
@@ -79,6 +81,8 @@ export function translateToNewRESTCollection(x: any): HoppCollection {
* @returns The proper new collection format * @returns The proper new collection format
*/ */
export function translateToNewGQLCollection(x: any): HoppCollection { export function translateToNewGQLCollection(x: any): HoppCollection {
if (x.v && x.v === CollectionSchemaVersion) return x
// Legacy // Legacy
const name = x.name ?? "Untitled" const name = x.name ?? "Untitled"
const folders = (x.folders ?? []).map(translateToNewGQLCollection) const folders = (x.folders ?? []).map(translateToNewGQLCollection)

View File

@@ -5,7 +5,7 @@ import { InferredEntity, createVersionedEntity } from "verzod"
import { z } from "zod" import { z } from "zod"
import V0_VERSION from "./v/0" import V0_VERSION from "./v/0"
import V1_VERSION, { uniqueID } from "./v/1" import V1_VERSION from "./v/1"
const versionedObject = z.object({ const versionedObject = z.object({
v: z.number(), v: z.number(),
@@ -165,7 +165,7 @@ export const translateToNewEnvironment = (x: any): Environment => {
if (x.v && x.v === EnvironmentSchemaVersion) return x if (x.v && x.v === EnvironmentSchemaVersion) return x
// Legacy // Legacy
const id = x.id || uniqueID() const id = x.id ?? ""
const name = x.name ?? "Untitled" const name = x.name ?? "Untitled"
const variables = (x.variables ?? []).map(translateToNewEnvironmentVariables) const variables = (x.variables ?? []).map(translateToNewEnvironmentVariables)

View File

@@ -2,8 +2,6 @@ import { z } from "zod"
import { defineVersion } from "verzod" import { defineVersion } from "verzod"
import { V0_SCHEMA } from "./0" import { V0_SCHEMA } from "./0"
export const uniqueID = () => Math.random().toString(36).substring(2, 16)
export const V1_SCHEMA = z.object({ export const V1_SCHEMA = z.object({
v: z.literal(1), v: z.literal(1),
id: z.string(), id: z.string(),
@@ -30,7 +28,7 @@ export default defineVersion({
const result: z.infer<typeof V1_SCHEMA> = { const result: z.infer<typeof V1_SCHEMA> = {
...old, ...old,
v: 1, v: 1,
id: old.id || uniqueID(), id: old.id ?? "",
variables: old.variables.map((variable) => { variables: old.variables.map((variable) => {
return { return {
...variable, ...variable,

View File

@@ -18,8 +18,22 @@ export const HoppRESTRequestVariables = z.array(
export type HoppRESTRequestVariables = z.infer<typeof HoppRESTRequestVariables> export type HoppRESTRequestVariables = z.infer<typeof HoppRESTRequestVariables>
const V2_SCHEMA = V1_SCHEMA.extend({ const V2_SCHEMA = z.object({
v: z.literal("2"), v: z.literal("2"),
id: z.optional(z.string()), // Firebase Firestore ID
name: z.string(),
method: z.string(),
endpoint: z.string(),
params: HoppRESTParams,
headers: HoppRESTHeaders,
preRequestScript: z.string().catch(""),
testScript: z.string().catch(""),
auth: HoppRESTAuth,
body: HoppRESTReqBody,
requestVariables: HoppRESTRequestVariables, requestVariables: HoppRESTRequestVariables,
}) })

View File

@@ -52,27 +52,27 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "^4.17.12",
"fp-ts": "2.12.1", "fp-ts": "^2.11.10",
"lodash": "4.17.21", "lodash": "^4.17.21",
"lodash-es": "4.17.21" "lodash-es": "^4.17.21"
}, },
"devDependencies": { "devDependencies": {
"@digitak/esrun": "3.2.8", "@digitak/esrun": "^3.1.2",
"@relmify/jest-fp-ts": "2.0.2", "@relmify/jest-fp-ts": "^2.0.1",
"@types/jest": "27.5.2", "@types/jest": "^27.4.1",
"@types/lodash": "4.14.182", "@types/lodash": "^4.14.181",
"@types/node": "17.0.45", "@types/node": "^17.0.24",
"@typescript-eslint/eslint-plugin": "5.30.6", "@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "5.30.6", "@typescript-eslint/parser": "^5.19.0",
"eslint": "8.19.0", "eslint": "^8.13.0",
"eslint-config-prettier": "8.6.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "^4.2.1",
"io-ts": "2.2.16", "io-ts": "^2.2.16",
"jest": "27.5.1", "jest": "^27.5.1",
"prettier": "2.8.4", "prettier": "^2.8.4",
"ts-jest": "27.1.5", "ts-jest": "^27.1.4",
"typescript": "4.9.5", "typescript": "^4.6.3",
"vite": "5.0.5" "vite": "^5.0.4"
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/selfhost-desktop", "name": "@hoppscotch/selfhost-desktop",
"private": true, "private": true,
"version": "2024.3.0", "version": "2023.12.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:vite": "vite", "dev:vite": "vite",
@@ -13,69 +13,69 @@
"gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\"" "gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\""
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.0.15", "@fontsource-variable/inter": "^5.0.5",
"@fontsource-variable/material-symbols-rounded": "5.0.16", "@fontsource-variable/material-symbols-rounded": "^5.0.5",
"@fontsource-variable/roboto-mono": "5.0.16", "@fontsource-variable/roboto-mono": "^5.0.6",
"@hoppscotch/common": "workspace:^", "@hoppscotch/common": "workspace:^",
"@platform/auth": "0.1.106", "@platform/auth": "^0.1.106",
"@tauri-apps/api": "1.5.1", "@tauri-apps/api": "^1.3.0",
"@tauri-apps/cli": "1.5.6", "@tauri-apps/cli": "^1.3.0",
"@vueuse/core": "10.5.0", "@vueuse/core": "^10.4.1",
"axios": "0.21.4", "axios": "^0.21.4",
"buffer": "6.0.3", "buffer": "^6.0.3",
"dioc": "1.0.1", "dioc": "^1.0.1",
"environments.api": "link:@platform/environments/environments.api", "environments.api": "link:@platform/environments/environments.api",
"event": "link:@tauri-apps/api/event", "event": "link:@tauri-apps/api/event",
"fp-ts": "2.16.1", "fp-ts": "^2.16.0",
"lodash-es": "4.17.21", "lodash-es": "^4.17.21",
"process": "0.11.10", "process": "^0.11.10",
"rxjs": "7.8.1", "rxjs": "^7.8.1",
"shell": "link:@tauri-apps/api/shell", "shell": "link:@tauri-apps/api/shell",
"stream-browserify": "3.0.0", "stream-browserify": "^3.0.0",
"tauri": "link:@tauri-apps/api/tauri", "tauri": "link:@tauri-apps/api/tauri",
"tauri-plugin-store-api": "0.0.0", "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
"util": "0.12.5", "util": "^0.12.4",
"vue": "3.3.9", "vue": "^3.2.45",
"workbox-window": "6.6.0" "workbox-window": "^6.5.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/add": "5.0.0", "@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "5.0.0", "@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "^5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-urql-graphcache": "2.4.5", "@graphql-codegen/typescript-urql-graphcache": "^2.4.5",
"@graphql-codegen/urql-introspection": "2.2.1", "@graphql-codegen/urql-introspection": "^2.2.1",
"@graphql-typed-document-node/core": "3.2.0", "@graphql-typed-document-node/core": "^3.2.0",
"@intlify/vite-plugin-vue-i18n": "6.0.1", "@intlify/vite-plugin-vue-i18n": "^6.0.1",
"@rushstack/eslint-patch": "1.3.3", "@rushstack/eslint-patch": "^1.1.4",
"@types/lodash-es": "4.17.10", "@types/lodash-es": "^4.17.9",
"@types/node": "18.18.8", "@types/node": "^18.7.10",
"@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "5.62.0", "@typescript-eslint/parser": "^5.19.0",
"@vitejs/plugin-legacy": "2.3.0", "@vitejs/plugin-legacy": "^2.3.0",
"@vitejs/plugin-vue": "4.3.1", "@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-typescript": "11.0.3", "@vue/eslint-config-typescript": "^11.0.1",
"autoprefixer": "10.4.16", "autoprefixer": "^10.4.14",
"cross-env": "7.0.3", "cross-env": "^7.0.3",
"eslint": "8.47.0", "eslint": "^8.28.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "9.17.0", "eslint-plugin-vue": "^9.5.1",
"npm-run-all": "4.1.5", "npm-run-all": "^4.1.5",
"postcss": "8.4.32", "postcss": "^8.4.23",
"tailwindcss": "3.3.6", "tailwindcss": "^3.3.6",
"typescript": "4.9.5", "typescript": "^4.9.5",
"unplugin-fonts": "1.1.1", "unplugin-fonts": "^1.1.1",
"unplugin-icons": "0.14.9", "unplugin-icons": "^0.14.9",
"unplugin-vue-components": "0.21.0", "unplugin-vue-components": "^0.21.0",
"vite": "4.5.0", "vite": "^4.2.1",
"vite-plugin-html-config": "1.0.11", "vite-plugin-html-config": "^1.0.10",
"vite-plugin-inspect": "0.7.38", "vite-plugin-inspect": "^0.7.4",
"vite-plugin-pages": "0.26.0", "vite-plugin-pages": "^0.26.0",
"vite-plugin-pages-sitemap": "1.6.1", "vite-plugin-pages-sitemap": "^1.4.0",
"vite-plugin-pwa": "0.13.1", "vite-plugin-pwa": "^0.13.1",
"vite-plugin-static-copy": "0.12.0", "vite-plugin-static-copy": "^0.12.0",
"vite-plugin-vue-layouts": "0.7.0", "vite-plugin-vue-layouts": "^0.7.0",
"vue-tsc": "1.8.8" "vue-tsc": "^1.0.11"
} }
} }

View File

@@ -1260,7 +1260,7 @@ dependencies = [
[[package]] [[package]]
name = "hoppscotch-desktop" name = "hoppscotch-desktop"
version = "24.3.0" version = "23.12.6"
dependencies = [ dependencies = [
"cocoa 0.25.0", "cocoa 0.25.0",
"hex_color", "hex_color",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "hoppscotch-desktop" name = "hoppscotch-desktop"
version = "24.3.0" version = "23.12.6"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
license = "" license = ""

View File

@@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "Hoppscotch", "productName": "Hoppscotch",
"version": "24.3.0" "version": "23.12.6"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -112,15 +112,10 @@ function exportedCollectionToHoppCollection(
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: restCollection.requests.map((request) => { requests: restCollection.requests.map(
const requestParsedResult = HoppRESTRequest.safeParse(request) ({
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const {
v,
id, id,
v,
auth, auth,
body, body,
endpoint, endpoint,
@@ -130,23 +125,20 @@ function exportedCollectionToHoppCollection(
params, params,
preRequestScript, preRequestScript,
testScript, testScript,
requestVariables, }) => ({
} = request
return {
v,
id, id,
name, v,
endpoint,
method,
params,
requestVariables: requestVariables,
auth, auth,
headers,
body, body,
endpoint,
headers,
method,
name,
params,
preRequestScript, preRequestScript,
testScript, testScript,
} })
}), ),
} }
} else { } else {
const gqlCollection = collection as ExportedUserCollectionGQL const gqlCollection = collection as ExportedUserCollectionGQL

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/selfhost-web", "name": "@hoppscotch/selfhost-web",
"private": true, "private": true,
"version": "2024.3.0", "version": "2023.12.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:vite": "vite", "dev:vite": "vite",
@@ -23,62 +23,62 @@
"postinstall": "pnpm run gql-codegen" "postinstall": "pnpm run gql-codegen"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.0.15", "@fontsource-variable/inter": "^5.0.15",
"@fontsource-variable/material-symbols-rounded": "5.0.16", "@fontsource-variable/material-symbols-rounded": "^5.0.16",
"@fontsource-variable/roboto-mono": "5.0.16", "@fontsource-variable/roboto-mono": "^5.0.16",
"@hoppscotch/common": "workspace:^", "@hoppscotch/common": "workspace:^",
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/ui": "0.1.0", "@hoppscotch/ui": "^0.1.0",
"@import-meta-env/unplugin": "0.4.10", "@import-meta-env/unplugin": "^0.4.10",
"axios": "1.6.2", "axios": "^1.6.2",
"buffer": "6.0.3", "buffer": "^6.0.3",
"fp-ts": "2.16.1", "fp-ts": "^2.16.1",
"process": "0.11.10", "process": "^0.11.10",
"rxjs": "7.8.1", "rxjs": "^7.8.1",
"stream-browserify": "3.0.0", "stream-browserify": "^3.0.0",
"util": "0.12.5", "util": "^0.12.5",
"vue": "3.3.9", "vue": "^3.3.8",
"workbox-window": "7.0.0", "workbox-window": "^7.0.0",
"zod": "3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/add": "5.0.0", "@graphql-codegen/add": "^5.0.0",
"@graphql-codegen/cli": "5.0.0", "@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/typed-document-node": "5.0.1", "@graphql-codegen/typed-document-node": "^5.0.1",
"@graphql-codegen/typescript": "4.0.1", "@graphql-codegen/typescript": "^4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1", "@graphql-codegen/typescript-operations": "^4.0.1",
"@graphql-codegen/typescript-urql-graphcache": "3.0.0", "@graphql-codegen/typescript-urql-graphcache": "^3.0.0",
"@graphql-codegen/urql-introspection": "3.0.0", "@graphql-codegen/urql-introspection": "^3.0.0",
"@graphql-typed-document-node/core": "3.2.0", "@graphql-typed-document-node/core": "^3.2.0",
"@intlify/vite-plugin-vue-i18n": "7.0.0", "@intlify/vite-plugin-vue-i18n": "^7.0.0",
"@rushstack/eslint-patch": "1.6.0", "@rushstack/eslint-patch": "^1.6.0",
"@typescript-eslint/eslint-plugin": "6.13.2", "@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "6.13.2", "@typescript-eslint/parser": "^6.12.0",
"@vitejs/plugin-legacy": "4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "4.5.1", "@vitejs/plugin-vue": "^4.5.0",
"@vue/eslint-config-typescript": "12.0.0", "@vue/eslint-config-typescript": "^12.0.0",
"autoprefixer": "10.4.16", "autoprefixer": "^10.4.14",
"cross-env": "7.0.3", "cross-env": "^7.0.3",
"eslint": "8.55.0", "eslint": "^8.54.0",
"eslint-plugin-prettier": "5.0.1", "eslint-plugin-prettier": "^5.0.1",
"eslint-plugin-vue": "9.19.2", "eslint-plugin-vue": "^9.18.1",
"npm-run-all": "4.1.5", "npm-run-all": "^4.1.5",
"postcss": "8.4.31", "postcss": "^8.4.23",
"prettier-plugin-tailwindcss": "0.5.7", "prettier-plugin-tailwindcss": "^0.5.7",
"tailwindcss": "3.3.5", "tailwindcss": "^3.3.2",
"typescript": "5.3.2", "typescript": "^5.3.2",
"unplugin-fonts": "1.1.1", "unplugin-fonts": "^1.1.1",
"unplugin-icons": "0.17.4", "unplugin-icons": "^0.17.4",
"unplugin-vue-components": "0.25.2", "unplugin-vue-components": "^0.25.2",
"vite": "4.5.0", "vite": "^4.5.0",
"vite-plugin-fonts": "0.7.0", "vite-plugin-fonts": "^0.7.0",
"vite-plugin-html-config": "1.0.11", "vite-plugin-html-config": "^1.0.11",
"vite-plugin-inspect": "0.7.42", "vite-plugin-inspect": "^0.7.42",
"vite-plugin-pages": "0.31.0", "vite-plugin-pages": "^0.31.0",
"vite-plugin-pages-sitemap": "1.6.1", "vite-plugin-pages-sitemap": "^1.6.1",
"vite-plugin-pwa": "0.17.3", "vite-plugin-pwa": "^0.17.0",
"vite-plugin-static-copy": "0.17.1", "vite-plugin-static-copy": "^0.17.1",
"vite-plugin-vue-layouts": "0.8.0", "vite-plugin-vue-layouts": "^0.8.0",
"vue-tsc": "1.8.24" "vue-tsc": "^1.8.22"
} }
} }

View File

@@ -120,15 +120,10 @@ function exportedCollectionToHoppCollection(
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: restCollection.requests.map((request) => { requests: restCollection.requests.map(
const requestParsedResult = HoppRESTRequest.safeParse(request) ({
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const {
v,
id, id,
v,
auth, auth,
body, body,
endpoint, endpoint,
@@ -139,22 +134,21 @@ function exportedCollectionToHoppCollection(
preRequestScript, preRequestScript,
testScript, testScript,
requestVariables, requestVariables,
} = request }) => ({
return {
v,
id, id,
name, v,
endpoint,
method,
params,
requestVariables: requestVariables,
auth, auth,
headers,
body, body,
endpoint,
headers,
method,
name,
params,
preRequestScript, preRequestScript,
testScript, testScript,
} requestVariables,
}), })
),
auth: data.auth, auth: data.auth,
headers: data.headers, headers: data.headers,
} }

View File

@@ -10,12 +10,9 @@
}, },
"configs": { "configs": {
"auth_providers": { "auth_providers": {
"callback_url": "CALLBACK URL",
"client_id": "CLIENT ID", "client_id": "CLIENT ID",
"client_secret": "CLIENT SECRET", "client_secret": "CLIENT SECRET",
"description": "Configure authentication providers for your server", "description": "Configure authentication providers for your server",
"scope": "SCOPE",
"tenant": "TENANT",
"title": "Authentication Providers", "title": "Authentication Providers",
"update_failure": "Failed to update authentication provider configurations!!" "update_failure": "Failed to update authentication provider configurations!!"
}, },
@@ -106,18 +103,8 @@
"admin_failure": "Failed to make user an admin!!", "admin_failure": "Failed to make user an admin!!",
"admin_success": "User is now an admin!!", "admin_success": "User is now an admin!!",
"and": "and", "and": "and",
"clear_selection": "Clear Selection",
"configure_auth": "Check out the documentation to configure auth providers.", "configure_auth": "Check out the documentation to configure auth providers.",
"confirm_admin_to_user": "Do you want to remove admin status from this user?",
"confirm_admins_to_users": "Do you want to remove admin status from selected users?",
"confirm_delete_invite": "Do you want to revoke the selected invite?",
"confirm_delete_invites": "Do you want to revoke selected invites?",
"confirm_user_deletion": "Confirm user deletion?",
"confirm_users_deletion": "Do you want to delete selected users?",
"confirm_user_to_admin": "Do you want to make this user into an admin?",
"confirm_users_to_admin": "Do you want to make selected users into admins?",
"confirm_logout": "Confirm Logout", "confirm_logout": "Confirm Logout",
"created_on": "Created On",
"continue_email": "Continue with Email", "continue_email": "Continue with Email",
"continue_github": "Continue with Github", "continue_github": "Continue with Github",
"continue_google": "Continue with Google", "continue_google": "Continue with Google",
@@ -126,21 +113,12 @@
"create_team_failure": "Failed to create workspace!!", "create_team_failure": "Failed to create workspace!!",
"create_team_success": "Workspace created successfully!!", "create_team_success": "Workspace created successfully!!",
"data_sharing_failure": "Failed to update data sharing settings", "data_sharing_failure": "Failed to update data sharing settings",
"delete_invite_failure": "Failed to delete invite!!",
"delete_invites_failure": "Failed to delete selected invites!!",
"delete_invite_success": "Invite deleted successfully!!",
"delete_invites_success": "Selected invites deleted successfully!!",
"delete_request_failure": "Shared Request deletion failed!!", "delete_request_failure": "Shared Request deletion failed!!",
"delete_request_success": "Shared Request deleted successfully!!", "delete_request_success": "Shared Request deleted successfully!!",
"delete_team_failure": "Workspace deletion failed!!", "delete_team_failure": "Workspace deletion failed!!",
"delete_team_success": "Workspace deleted successfully!!", "delete_team_success": "Workspace deleted successfully!!",
"delete_some_users_failure": "Number of Users Not Deleted: {count}",
"delete_some_users_success": "Number of Users Deleted: {count}",
"delete_user_failed_only_one_admin": "Failed to delete user. There should be atleast one admin!!",
"delete_user_failure": "User deletion failed!!", "delete_user_failure": "User deletion failed!!",
"delete_users_failure": "Failed to delete selected users!!",
"delete_user_success": "User deleted successfully!!", "delete_user_success": "User deleted successfully!!",
"delete_users_success": "Selected users deleted successfully!!",
"email": "Email", "email": "Email",
"email_failure": "Failed to send invitation", "email_failure": "Failed to send invitation",
"email_signin_failure": "Failed to login with Email", "email_signin_failure": "Failed to login with Email",
@@ -165,22 +143,16 @@
"reenter_email": "Re-enter email", "reenter_email": "Re-enter email",
"remove_admin_failure": "Failed to remove admin status!!", "remove_admin_failure": "Failed to remove admin status!!",
"remove_admin_success": "Admin status removed!!", "remove_admin_success": "Admin status removed!!",
"remove_admin_from_users_failure": "Failed to remove admin status from selected users!!",
"remove_admin_from_users_success": "Admin status removed from selected users!!",
"remove_admin_to_delete_user": "Remove admin privilege to delete the user!!", "remove_admin_to_delete_user": "Remove admin privilege to delete the user!!",
"remove_admin_for_deletion": "Remove admin status before attempting deletion!!",
"remove_invitee_failure": "Removal of invitee failed!!", "remove_invitee_failure": "Removal of invitee failed!!",
"remove_invitee_success": "Removal of invitee is successfull!!", "remove_invitee_success": "Removal of invitee is successfull!!",
"remove_member_failure": "Member couldn't be removed!!", "remove_member_failure": "Member couldn't be removed!!",
"remove_member_success": "Member removed successfully!!", "remove_member_success": "Member removed successfully!!",
"rename_team_failure": "Failed to rename workspace!!", "rename_team_failure": "Failed to rename workspace!!",
"rename_team_success": "Workspace renamed successfully!", "rename_team_success": "Workspace renamed successfully!",
"rename_user_failure": "Failed to rename user!!",
"rename_user_success": "User renamed successfully!!",
"require_auth_provider": "You need to set atleast one authentication provider to log in.", "require_auth_provider": "You need to set atleast one authentication provider to log in.",
"role_update_failed": "Roles updation has failed!!", "role_update_failed": "Roles updation has failed!!",
"role_update_success": "Roles updated successfully!!", "role_update_success": "Roles updated successfully!!",
"selected": "{count} selected",
"self_host_docs": "Self Host Documentation", "self_host_docs": "Self Host Documentation",
"send_magic_link": "Send magic link", "send_magic_link": "Send magic link",
"setup_failure": "Setup has failed!!", "setup_failure": "Setup has failed!!",
@@ -189,11 +161,7 @@
"sign_in_options": "All sign in option", "sign_in_options": "All sign in option",
"sign_out": "Sign out", "sign_out": "Sign out",
"team_name_too_short": "Workspace name should be atleast 6 characters long!!", "team_name_too_short": "Workspace name should be atleast 6 characters long!!",
"team_name_long": "Workspace name should be atleast 6 characters long!!", "user_not_found": "User not found in the infra!!"
"user_already_invited": "Failed to send invite. User is already invited!!",
"user_not_found": "User not found in the infra!!",
"users_to_admin_success": "Selected users are elevated to admin status!!",
"users_to_admin_failure": "Failed to elevate selected users to admin status!!"
}, },
"teams": { "teams": {
"add_member": "Add Member", "add_member": "Add Member",
@@ -230,7 +198,7 @@
"name": "Workspace Name", "name": "Workspace Name",
"no_members": "No members in this workspace. Add members to this workspace to collaborate", "no_members": "No members in this workspace. Add members to this workspace to collaborate",
"no_pending_invites": "No pending invites", "no_pending_invites": "No pending invites",
"no_teams": "No workspaces found..", "no_teams": "No workspaces found",
"pending_invites": "Pending invites", "pending_invites": "Pending invites",
"roles": "Roles", "roles": "Roles",
"roles_description": "Roles are used to control access to the shared collections.", "roles_description": "Roles are used to control access to the shared collections.",
@@ -255,17 +223,16 @@
"admin": "Admin", "admin": "Admin",
"admin_email": "Admin Email", "admin_email": "Admin Email",
"admin_id": "Admin ID", "admin_id": "Admin ID",
"cancel": "Cancel", "confirm_admin_to_user": "Do you want to remove admin status from this user?",
"confirm_user_deletion": "Confirm user deletion?",
"confirm_user_to_admin": "Do you want to make this user into an admin?",
"created_on": "Created On", "created_on": "Created On",
"date": "Date", "date": "Date",
"delete": "Delete", "delete": "Delete",
"delete_user": "Delete User", "delete_user": "Delete User",
"delete_users": "Delete Users",
"details": "Details", "details": "Details",
"edit": "Edit",
"email": "Email", "email": "Email",
"email_address": "Email Address", "email_address": "Email Address",
"empty_name": "Name cannot be empty!!",
"id": "User ID", "id": "User ID",
"invalid_user": "Invalid User", "invalid_user": "Invalid User",
"invite_load_list_error": "Unable to Load Invited Users List", "invite_load_list_error": "Unable to Load Invited Users List",
@@ -275,18 +242,14 @@
"invitee_email": "Invitee Email", "invitee_email": "Invitee Email",
"load_info_error": "Unable to load user info", "load_info_error": "Unable to load user info",
"load_list_error": "Unable to Load Users List", "load_list_error": "Unable to Load Users List",
"make_admin": "Make Admin", "make_admin": "Make admin",
"name": "Name", "name": "Name",
"no_invite": "No pending invites found", "no_invite": "No invited users found",
"no_shared_requests": "No shared requests created by the user", "no_shared_requests": "No shared requests created by the user",
"no_users": "No users found", "no_users": "No users found",
"not_found": "User not found", "not_found": "User not found",
"pending_invites": "Pending Invites",
"remove_admin_privilege": "Remove Admin Privilege", "remove_admin_privilege": "Remove Admin Privilege",
"remove_admin_status": "Remove Admin Status", "remove_admin_status": "Remove Admin Status",
"rename": "Rename",
"revoke_invitation": "Revoke Invitation",
"searchbar_placeholder": "Search by name or email..",
"send_invite": "Send Invite", "send_invite": "Send Invite",
"show_more": "Show more", "show_more": "Show more",
"uid": "UID", "uid": "UID",

View File

@@ -1,7 +1,7 @@
{ {
"name": "hoppscotch-sh-admin", "name": "hoppscotch-sh-admin",
"private": true, "private": true,
"version": "2024.3.0", "version": "2023.12.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
@@ -13,66 +13,66 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@fontsource-variable/inter": "5.0.5", "@fontsource-variable/inter": "^5.0.5",
"@fontsource-variable/material-symbols-rounded": "5.0.5", "@fontsource-variable/material-symbols-rounded": "^5.0.5",
"@fontsource-variable/roboto-mono": "5.0.6", "@fontsource-variable/roboto-mono": "^5.0.6",
"@graphql-typed-document-node/core": "3.1.1", "@graphql-typed-document-node/core": "^3.1.1",
"@hoppscotch/ui": "0.1.2", "@hoppscotch/ui": "^0.1.0",
"@hoppscotch/vue-toasted": "0.1.0", "@hoppscotch/vue-toasted": "^0.1.0",
"@intlify/unplugin-vue-i18n": "1.2.0", "@intlify/unplugin-vue-i18n": "^1.2.0",
"@types/cors": "2.8.13", "@types/cors": "^2.8.13",
"@types/express": "4.17.16", "@types/express": "^4.17.15",
"@urql/exchange-auth": "2.1.6", "@urql/exchange-auth": "^2.1.6",
"@urql/vue": "1.1.2", "@urql/vue": "^1.1.2",
"@vueuse/core": "9.12.0", "@vueuse/core": "^9.10.0",
"axios": "0.21.4", "axios": "^0.21.4",
"cors": "2.8.5", "cors": "^2.8.5",
"date-fns": "2.29.3", "date-fns": "^2.29.3",
"express": "4.18.2", "express": "^4.18.2",
"express-graphql": "0.12.0", "express-graphql": "^0.12.0",
"fp-ts": "2.13.1", "fp-ts": "^2.13.1",
"graphql": "16.6.0", "graphql": "^16.6.0",
"io-ts": "2.2.16", "io-ts": "^2.2.16",
"lodash-es": "4.17.21", "lodash-es": "^4.17.21",
"postcss": "8.4.31", "postcss": "^8.4.23",
"prettier-plugin-tailwindcss": "0.5.7", "prettier-plugin-tailwindcss": "^0.5.6",
"rxjs": "7.8.0", "rxjs": "^7.8.0",
"tailwindcss": "3.3.5", "tailwindcss": "^3.3.5",
"tippy.js": "6.3.7", "tippy.js": "^6.3.7",
"ts-node-dev": "2.0.0", "ts-node-dev": "^2.0.0",
"unplugin-icons": "0.14.9", "unplugin-icons": "^0.14.9",
"unplugin-vue-components": "0.21.0", "unplugin-vue-components": "^0.21.0",
"vue": "3.3.9", "vue": "^3.2.6",
"vue-i18n": "9.2.2", "vue-i18n": "^9.2.2",
"vue-router": "4.1.0", "vue-router": "4",
"vue-tippy": "6.0.0-alpha.58" "vue-tippy": "6.0.0-alpha.58"
}, },
"devDependencies": { "devDependencies": {
"hoppscotch-backend": "workspace:^", "hoppscotch-backend": "workspace:^",
"@graphql-codegen/cli": "3.0.0", "@graphql-codegen/cli": "3.0.0",
"@graphql-codegen/client-preset": "2.1.0", "@graphql-codegen/client-preset": "^2.0.0",
"@graphql-codegen/introspection": "3.0.0", "@graphql-codegen/introspection": "3.0.0",
"@graphql-codegen/typed-document-node": "2.3.1", "@graphql-codegen/typed-document-node": "^2.3.1",
"@graphql-codegen/typescript": "3.0.0", "@graphql-codegen/typescript": "3.0.0",
"@graphql-codegen/typescript-document-nodes": "3.0.0", "@graphql-codegen/typescript-document-nodes": "3.0.0",
"@graphql-codegen/typescript-operations": "3.0.0", "@graphql-codegen/typescript-operations": "3.0.0",
"@graphql-codegen/urql-introspection": "2.2.1", "@graphql-codegen/urql-introspection": "2.2.1",
"@import-meta-env/cli": "0.6.3", "@import-meta-env/cli": "^0.6.3",
"@import-meta-env/unplugin": "0.4.8", "@import-meta-env/unplugin": "^0.4.8",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "^4.17.12",
"@vitejs/plugin-vue": "3.2.0", "@vitejs/plugin-vue": "^3.1.0",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "^3.2.6",
"dotenv": "16.3.1", "dotenv": "^16.0.3",
"graphql-tag": "2.12.6", "graphql-tag": "^2.12.6",
"npm-run-all": "4.1.5", "npm-run-all": "^4.1.5",
"sass": "1.58.0", "sass": "^1.57.1",
"ts-node": "10.9.1", "ts-node": "^10.9.1",
"typescript": "4.9.3", "typescript": "^4.9.3",
"unplugin-fonts": "1.0.3", "unplugin-fonts": "^1.0.3",
"vite": "3.2.4", "vite": "^3.1.4",
"vite-plugin-pages": "0.26.0", "vite-plugin-pages": "^0.26.0",
"vite-plugin-vue-layouts": "0.7.0", "vite-plugin-vue-layouts": "^0.7.0",
"vue-tsc": "0.3.0" "vue-tsc": "^0.3.0"
}, },
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,

View File

@@ -17,23 +17,14 @@ declare module '@vue/runtime-core' {
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'] HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default'] SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default'] SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default'] SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
@@ -51,7 +42,6 @@ declare module '@vue/runtime-core' {
UsersDetails: typeof import('./components/users/Details.vue')['default'] UsersDetails: typeof import('./components/users/Details.vue')['default']
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'] UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default'] UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default']
UsersTable: typeof import('./components/users/Table.vue')['default']
} }
} }

View File

@@ -35,28 +35,25 @@
:key="field.key" :key="field.key"
class="mt-5" class="mt-5"
> >
<template <label>{{ field.name }}</label>
v-if="field.applicableProviders.includes(provider.name)" <span class="flex">
> <HoppSmartInput
<label>{{ field.name }}</label> v-model="provider.fields[field.key]"
<span class="flex max-w-lg"> :type="
<HoppSmartInput isMasked(provider.name, field.key) ? 'password' : 'text'
v-model="provider.fields[field.key as keyof typeof provider['fields']]" "
:type=" :disabled="isMasked(provider.name, field.key)"
isMasked(provider.name, field.key) ? 'password' : 'text' :autofocus="false"
" class="!my-2 !bg-primaryLight"
:autofocus="false" />
class="!my-2 !bg-primaryLight flex-1" <HoppButtonSecondary
/> :icon="
<HoppButtonSecondary isMasked(provider.name, field.key) ? IconEye : IconEyeOff
:icon=" "
isMasked(provider.name, field.key) ? IconEye : IconEyeOff class="bg-primaryLight h-9 mt-2"
" @click="toggleMask(provider.name, field.key)"
class="bg-primaryLight h-9 mt-2" />
@click="toggleMask(provider.name, field.key)" </span>
/>
</span>
</template>
</div> </div>
</div> </div>
</div> </div>
@@ -89,76 +86,47 @@ const workingConfigs = useVModel(props, 'config', emit);
const capitalize = (text: string) => const capitalize = (text: string) =>
text.charAt(0).toUpperCase() + text.slice(1); text.charAt(0).toUpperCase() + text.slice(1);
// Union type for all possible field keys // Masking sensitive fields
type ProviderFieldKeys = keyof ProviderFields; type Field = {
type ProviderFields = {
[Field in keyof Config['providers'][SsoAuthProviders]['fields']]: boolean;
} & Partial<{ tenant: boolean }>;
type ProviderFieldMetadata = {
name: string; name: string;
key: ProviderFieldKeys; key: keyof Config['providers']['google' | 'github' | 'microsoft']['fields'];
applicableProviders: SsoAuthProviders[];
}; };
const providerConfigFields = <ProviderFieldMetadata[]>[ const providerConfigFields = reactive<Field[]>([
{ { name: t('configs.auth_providers.client_id'), key: 'client_id' },
name: t('configs.auth_providers.client_id'), { name: t('configs.auth_providers.client_secret'), key: 'client_secret' },
key: 'client_id', ]);
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.client_secret'),
key: 'client_secret',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.callback_url'),
key: 'callback_url',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.scope'),
key: 'scope',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.tenant'),
key: 'tenant',
applicableProviders: ['microsoft'],
},
];
const maskState = reactive<Record<SsoAuthProviders, ProviderFields>>({ const maskState = reactive({
google: { google: {
client_id: true, client_id: true,
client_secret: true, client_secret: true,
callback_url: true,
scope: true,
}, },
github: { github: {
client_id: true, client_id: true,
client_secret: true, client_secret: true,
callback_url: true,
scope: true,
}, },
microsoft: { microsoft: {
client_id: true, client_id: true,
client_secret: true, client_secret: true,
callback_url: true,
scope: true,
tenant: true,
}, },
}); });
const toggleMask = ( const toggleMask = (
provider: SsoAuthProviders, provider: SsoAuthProviders,
fieldKey: ProviderFieldKeys fieldKey: keyof Config['providers'][
| 'google'
| 'github'
| 'microsoft']['fields']
) => { ) => {
maskState[provider][fieldKey] = !maskState[provider][fieldKey]; maskState[provider][fieldKey] = !maskState[provider][fieldKey];
}; };
const isMasked = (provider: SsoAuthProviders, fieldKey: ProviderFieldKeys) => const isMasked = (
maskState[provider][fieldKey]; provider: SsoAuthProviders,
fieldKey: keyof Config['providers'][
| 'google'
| 'github'
| 'microsoft']['fields']
) => maskState[provider][fieldKey];
</script> </script>

View File

@@ -21,12 +21,13 @@
</HoppSmartToggle> </HoppSmartToggle>
</div> </div>
<!-- TODO: Update the link below -->
<HoppButtonSecondary <HoppButtonSecondary
outline outline
filled filled
:icon="IconShieldQuestion" :icon="IconShieldQuestion"
:label="t('configs.data_sharing.see_shared')" :label="t('configs.data_sharing.see_shared')"
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/telemetry" to="http://docs.hoppscotch.io"
blank blank
class="w-min my-2" class="w-min my-2"
/> />

View File

@@ -33,12 +33,13 @@
class="mt-5" class="mt-5"
> >
<label>{{ field.name }}</label> <label>{{ field.name }}</label>
<span class="flex max-w-lg"> <span class="flex">
<HoppSmartInput <HoppSmartInput
v-model="smtpConfigs.fields[field.key]" v-model="smtpConfigs.fields[field.key]"
:type="isMasked(field.key) ? 'password' : 'text'" :type="isMasked(field.key) ? 'password' : 'text'"
:disabled="isMasked(field.key)"
:autofocus="false" :autofocus="false"
class="!my-2 !bg-primaryLight flex-1" class="!my-2 !bg-primaryLight"
/> />
<HoppButtonSecondary <HoppButtonSecondary
:icon="isMasked(field.key) ? IconEye : IconEyeOff" :icon="isMasked(field.key) ? IconEye : IconEyeOff"

View File

@@ -28,9 +28,10 @@
> >
{{ t('data_sharing.toggle_description') }} {{ t('data_sharing.toggle_description') }}
</HoppSmartToggle> </HoppSmartToggle>
<!-- TODO: Update link -->
<HoppSmartAnchor <HoppSmartAnchor
blank blank
to="https://docs.hoppscotch.io/documentation/self-host/community-edition/telemetry" to="http://docs.hoppscotch.io"
:label="t('data_sharing.see_shared')" :label="t('data_sharing.see_shared')"
class="underline" class="underline"
/> />

View File

@@ -24,40 +24,11 @@
</div> </div>
<template v-for="(info, key) in userInfo" :key="key"> <template v-for="(info, key) in userInfo" :key="key">
<div v-if="key === 'displayName'" class="flex flex-col space-y-3"> <div v-if="info.condition">
<label class="text-accentContrast" for="teamname"
>{{ t('users.name') }}
</label>
<div
class="flex bg-divider rounded-md items-stretch flex-1 border border-divider"
:class="{
'!border-accent': isNameBeingEdited,
}"
>
<HoppSmartInput
v-model="updatedUserName"
styles="bg-transparent flex-1 rounded-md !rounded-r-none disabled:select-none border-r-0 disabled:cursor-default disabled:opacity-50"
:placeholder="t('users.name')"
:disabled="!isNameBeingEdited"
>
<template #button>
<HoppButtonPrimary
class="!rounded-l-none"
filled
:icon="isNameBeingEdited ? IconSave : IconEdit"
:label="
isNameBeingEdited ? t('users.rename') : t('users.edit')
"
@click="handleNameEdit"
/>
</template>
</HoppSmartInput>
</div>
</div>
<div v-else-if="info.condition">
<label class="text-secondaryDark" :for="key">{{ info.label }}</label> <label class="text-secondaryDark" :for="key">{{ info.label }}</label>
<div class="w-full p-3 mt-2 bg-divider border-gray-600 rounded-md"> <div
class="w-full p-3 mt-2 bg-divider border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
>
<span>{{ info.value }}</span> <span>{{ info.value }}</span>
</div> </div>
</div> </div>
@@ -99,17 +70,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useMutation } from '@urql/vue';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast'; import { useToast } from '~/composables/toast';
import { import { UserInfoQuery } from '~/helpers/backend/graphql';
UpdateUserDisplayNameByAdminDocument,
UserInfoQuery,
} from '~/helpers/backend/graphql';
import IconEdit from '~icons/lucide/edit';
import IconSave from '~icons/lucide/save';
import IconTrash from '~icons/lucide/trash'; import IconTrash from '~icons/lucide/trash';
import IconUserCheck from '~icons/lucide/user-check'; import IconUserCheck from '~icons/lucide/user-check';
import IconUserMinus from '~icons/lucide/user-minus'; import IconUserMinus from '~icons/lucide/user-minus';
@@ -125,7 +89,6 @@ const emit = defineEmits<{
(event: 'delete-user', userID: string): void; (event: 'delete-user', userID: string): void;
(event: 'make-admin', userID: string): void; (event: 'make-admin', userID: string): void;
(event: 'remove-admin', userID: string): void; (event: 'remove-admin', userID: string): void;
(event: 'update-user-name', newName: string): void;
}>(); }>();
// Get Proper Date Formats // Get Proper Date Formats
@@ -157,62 +120,4 @@ const userInfo = {
value: getCreatedDateAndTime(createdOn), value: getCreatedDateAndTime(createdOn),
}, },
}; };
// Contains the actual user name
const userName = computed({
get: () => props.user.displayName,
set: (value) => {
return value;
},
});
// Contains the stored user name from the actual name before being edited
const currentUserName = ref('');
// Set the current user name to the actual user name
onMounted(() => {
if (displayName) currentUserName.value = displayName;
});
// Contains the user name that is being edited
const updatedUserName = computed({
get: () => currentUserName.value,
set: (value) => {
currentUserName.value = value;
},
});
// Rename the user
const isNameBeingEdited = ref(false);
const userRename = useMutation(UpdateUserDisplayNameByAdminDocument);
const handleNameEdit = () => {
if (isNameBeingEdited.value) {
// If the name is not changed, then return control
if (userName.value !== updatedUserName.value) {
renameUserName();
} else isNameBeingEdited.value = false;
} else {
isNameBeingEdited.value = true;
}
};
const renameUserName = async () => {
if (updatedUserName.value?.trim() === '') {
toast.error(t('users.empty_name'));
return;
}
const variables = { userUID: uid, name: updatedUserName.value as string };
const result = await userRename.executeMutation(variables);
if (result.error) {
toast.error(t('state.rename_user_failure'));
} else {
isNameBeingEdited.value = false;
toast.success(t('state.rename_user_success'));
emit('update-user-name', updatedUserName.value as string);
userName.value = updatedUserName.value;
}
};
</script> </script>

View File

@@ -10,7 +10,6 @@
v-model="email" v-model="email"
:label="t('users.email_address')" :label="t('users.email_address')"
input-styles="floating-input" input-styles="floating-input"
@submit="sendInvite"
/> />
</template> </template>
<template #footer> <template #footer>
@@ -19,12 +18,7 @@
:label="t('users.send_invite')" :label="t('users.send_invite')"
@click="sendInvite" @click="sendInvite"
/> />
<HoppButtonSecondary <HoppButtonSecondary label="Cancel" outline filled @click="hideModal" />
:label="t('users.cancel')"
outline
filled
@click="hideModal"
/>
</span> </span>
</template> </template>
</HoppSmartModal> </HoppSmartModal>

View File

@@ -1,68 +1,78 @@
<template> <template>
<div class="px-4 mt-7"> <div class="px-4">
<div v-if="fetching" class="flex justify-center"> <div v-if="fetching" class="flex justify-center">
<HoppSmartSpinner /> <HoppSmartSpinner />
</div> </div>
<div v-else-if="error">{{ t('shared_requests.load_list_error') }}</div> <div v-else-if="error">{{ t('shared_requests.load_list_error') }}</div>
<div v-else-if="sharedRequests.length === 0"> <div v-else-if="sharedRequests.length === 0" class="mt-5">
{{ t('users.no_shared_requests') }} {{ t('users.no_shared_requests') }}
</div> </div>
<HoppSmartTable v-else :headings="headings" :list="sharedRequests"> <HoppSmartTable v-else class="mt-8" :list="sharedRequests">
<template #head> <template #head>
<th class="px-6 py-2">{{ t('shared_requests.id') }}</th> <tr
<th class="px-6 py-2 w-30">{{ t('shared_requests.url') }}</th> class="text-secondary border-b border-dividerDark text-sm text-left bg-primaryLight"
<th class="px-6 py-2">{{ t('shared_requests.created_on') }}</th> >
<!-- Empty Heading for the Action Button --> <th class="px-6 py-2">{{ t('shared_requests.id') }}</th>
<th class="px-6 py-2 text-center"> <th class="px-6 py-2 w-30">{{ t('shared_requests.url') }}</th>
{{ t('shared_requests.action') }} <th class="px-6 py-2">{{ t('shared_requests.created_on') }}</th>
</th> <!-- Empty Heading for the Action Button -->
<th class="px-6 py-2 text-center">
{{ t('shared_requests.action') }}
</th>
</tr>
</template> </template>
<template #body="{ row: sharedRequest }"> <template #body="{ list: sharedRequests }">
<td class="flex py-4 px-7 max-w-50"> <tr
<span class="truncate"> v-for="request in sharedRequests"
{{ sharedRequest.id }} :key="request.id"
</span> class="text-secondaryDark hover:bg-divider hover:cursor-pointer rounded-xl"
</td> >
<td class="flex py-4 px-7 max-w-50">
<span class="truncate">
{{ request.id }}
</span>
</td>
<td class="py-4 px-7 w-96"> <td class="py-4 px-7 w-96">
{{ sharedRequestURL(sharedRequest.request) }} {{ sharedRequestURL(request.request) }}
</td> </td>
<td class="py-2 px-7"> <td class="py-2 px-7">
{{ getCreatedDate(sharedRequest.createdOn) }} {{ getCreatedDate(request.createdOn) }}
<div class="text-gray-400 text-tiny"> <div class="text-gray-400 text-tiny">
{{ getCreatedTime(sharedRequest.createdOn) }} {{ getCreatedTime(request.createdOn) }}
</div> </div>
</td> </td>
<td class="flex justify-center"> <td class="flex justify-center">
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('shared_requests.open_request')" :title="t('shared_requests.open_request')"
:to="`${shortcodeBaseURL}/r/${sharedRequest.id}`" :to="`${shortcodeBaseURL}/r/${request.id}`"
:blank="true" :blank="true"
:icon="IconExternalLink" :icon="IconExternalLink"
class="px-3 text-emerald-500 hover:text-accent" class="px-3 text-emerald-500 hover:text-accent"
/> />
<UiAutoResetIcon <UiAutoResetIcon
:title="t('shared_requests.copy')" :title="t('shared_requests.copy')"
:icon="{ default: IconCopy, temporary: IconCheck }" :icon="{ default: IconCopy, temporary: IconCheck }"
@click="copySharedRequest(sharedRequest.id)" @click="copySharedRequest(request.id)"
/> />
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('shared_requests.delete')" :title="t('shared_requests.delete')"
:icon="IconTrash" :icon="IconTrash"
color="red" color="red"
class="px-3" class="px-3"
@click="deleteSharedRequest(sharedRequest.id)" @click="deleteSharedRequest(request.id)"
/> />
</td> </td>
</tr>
</template> </template>
</HoppSmartTable> </HoppSmartTable>
@@ -126,18 +136,11 @@ const {
} = usePagedQuery( } = usePagedQuery(
SharedRequestsDocument, SharedRequestsDocument,
(x) => x.infra.allShortcodes, (x) => x.infra.allShortcodes,
(x) => x.id,
sharedRequestsPerPage, sharedRequestsPerPage,
{ cursor: undefined, take: sharedRequestsPerPage, email: props.email }, { cursor: undefined, take: sharedRequestsPerPage, email: props.email }
(x) => x.id
); );
const headings = [
{ key: 'id', label: t('shared_requests.id') },
{ key: 'request', label: t('shared_requests.url') },
{ key: 'createdOn', label: t('shared_requests.created_on') },
{ key: 'action', label: t('shared_requests.action') },
];
// Return request endpoint from the request object // Return request endpoint from the request object
const sharedRequestURL = (request: string) => { const sharedRequestURL = (request: string) => {
const parsedRequest = JSON.parse(request); const parsedRequest = JSON.parse(request);
@@ -171,17 +174,17 @@ const deleteSharedRequestMutation = async (id: string | null) => {
return; return;
} }
const variables = { codeID: id }; const variables = { codeID: id };
const result = await sharedRequestDeletion.executeMutation(variables); await sharedRequestDeletion.executeMutation(variables).then((result) => {
if (result.error) { if (result.error) {
toast.error(t('state.delete_request_failure')); toast.error(t('state.delete_request_failure'));
} else { } else {
sharedRequests.value = sharedRequests.value.filter( sharedRequests.value = sharedRequests.value.filter(
(request) => request.id !== id (request) => request.id !== id
); );
refetch(); refetch();
toast.success(t('state.delete_request_success')); toast.success(t('state.delete_request_success'));
} }
});
confirmDeletion.value = false; confirmDeletion.value = false;
deleteSharedRequestID.value = null; deleteSharedRequestID.value = null;
}; };

View File

@@ -27,8 +27,6 @@ export type Config = {
fields: { fields: {
client_id: string; client_id: string;
client_secret: string; client_secret: string;
callback_url: string;
scope: string;
}; };
}; };
github: { github: {
@@ -37,8 +35,6 @@ export type Config = {
fields: { fields: {
client_id: string; client_id: string;
client_secret: string; client_secret: string;
callback_url: string;
scope: string;
}; };
}; };
microsoft: { microsoft: {
@@ -47,9 +43,6 @@ export type Config = {
fields: { fields: {
client_id: string; client_id: string;
client_secret: string; client_secret: string;
callback_url: string;
scope: string;
tenant: string;
}; };
}; };
}; };
@@ -93,17 +86,10 @@ export function useConfigHandler(updatedConfigs?: Config) {
configNames: [ configNames: [
'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_ID',
'GOOGLE_CLIENT_SECRET', 'GOOGLE_CLIENT_SECRET',
'GOOGLE_CALLBACK_URL',
'GOOGLE_SCOPE',
'MICROSOFT_CLIENT_ID', 'MICROSOFT_CLIENT_ID',
'MICROSOFT_CLIENT_SECRET', 'MICROSOFT_CLIENT_SECRET',
'MICROSOFT_CALLBACK_URL',
'MICROSOFT_SCOPE',
'MICROSOFT_TENANT',
'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_ID',
'GITHUB_CLIENT_SECRET', 'GITHUB_CLIENT_SECRET',
'GITHUB_CALLBACK_URL',
'GITHUB_SCOPE',
'MAILER_SMTP_URL', 'MAILER_SMTP_URL',
'MAILER_ADDRESS_FROM', 'MAILER_ADDRESS_FROM',
'ALLOW_ANALYTICS_COLLECTION', 'ALLOW_ANALYTICS_COLLECTION',
@@ -132,9 +118,6 @@ export function useConfigHandler(updatedConfigs?: Config) {
await fetchInfraConfigs(); await fetchInfraConfigs();
await fetchAllowedAuthProviders(); await fetchAllowedAuthProviders();
const getFieldValue = (name: string) =>
infraConfigs.value.find((x) => x.name === name)?.value ?? '';
// Transforming the fetched data into a Configs object // Transforming the fetched data into a Configs object
currentConfigs.value = { currentConfigs.value = {
providers: { providers: {
@@ -142,31 +125,37 @@ export function useConfigHandler(updatedConfigs?: Config) {
name: 'google', name: 'google',
enabled: allowedAuthProviders.value.includes('GOOGLE'), enabled: allowedAuthProviders.value.includes('GOOGLE'),
fields: { fields: {
client_id: getFieldValue('GOOGLE_CLIENT_ID'), client_id:
client_secret: getFieldValue('GOOGLE_CLIENT_SECRET'), infraConfigs.value.find((x) => x.name === 'GOOGLE_CLIENT_ID')
callback_url: getFieldValue('GOOGLE_CALLBACK_URL'), ?.value ?? '',
scope: getFieldValue('GOOGLE_SCOPE'), client_secret:
infraConfigs.value.find((x) => x.name === 'GOOGLE_CLIENT_SECRET')
?.value ?? '',
}, },
}, },
github: { github: {
name: 'github', name: 'github',
enabled: allowedAuthProviders.value.includes('GITHUB'), enabled: allowedAuthProviders.value.includes('GITHUB'),
fields: { fields: {
client_id: getFieldValue('GITHUB_CLIENT_ID'), client_id:
client_secret: getFieldValue('GITHUB_CLIENT_SECRET'), infraConfigs.value.find((x) => x.name === 'GITHUB_CLIENT_ID')
callback_url: getFieldValue('GITHUB_CALLBACK_URL'), ?.value ?? '',
scope: getFieldValue('GITHUB_SCOPE'), client_secret:
infraConfigs.value.find((x) => x.name === 'GITHUB_CLIENT_SECRET')
?.value ?? '',
}, },
}, },
microsoft: { microsoft: {
name: 'microsoft', name: 'microsoft',
enabled: allowedAuthProviders.value.includes('MICROSOFT'), enabled: allowedAuthProviders.value.includes('MICROSOFT'),
fields: { fields: {
client_id: getFieldValue('MICROSOFT_CLIENT_ID'), client_id:
client_secret: getFieldValue('MICROSOFT_CLIENT_SECRET'), infraConfigs.value.find((x) => x.name === 'MICROSOFT_CLIENT_ID')
callback_url: getFieldValue('MICROSOFT_CALLBACK_URL'), ?.value ?? '',
scope: getFieldValue('MICROSOFT_SCOPE'), client_secret:
tenant: getFieldValue('MICROSOFT_TENANT'), infraConfigs.value.find(
(x) => x.name === 'MICROSOFT_CLIENT_SECRET'
)?.value ?? '',
}, },
}, },
}, },
@@ -174,8 +163,12 @@ export function useConfigHandler(updatedConfigs?: Config) {
name: 'email', name: 'email',
enabled: allowedAuthProviders.value.includes('EMAIL'), enabled: allowedAuthProviders.value.includes('EMAIL'),
fields: { fields: {
mailer_smtp_url: getFieldValue('MAILER_SMTP_URL'), mailer_smtp_url:
mailer_from_address: getFieldValue('MAILER_ADDRESS_FROM'), infraConfigs.value.find((x) => x.name === 'MAILER_SMTP_URL')
?.value ?? '',
mailer_from_address:
infraConfigs.value.find((x) => x.name === 'MAILER_ADDRESS_FROM')
?.value ?? '',
}, },
}, },
dataSharingConfigs: { dataSharingConfigs: {
@@ -191,7 +184,7 @@ export function useConfigHandler(updatedConfigs?: Config) {
workingConfigs.value = cloneDeep(currentConfigs.value); workingConfigs.value = cloneDeep(currentConfigs.value);
}); });
// Transforming the working configs back into the format required by the mutations // Trasforming the working configs back into the format required by the mutations
const updatedInfraConfigs = computed(() => { const updatedInfraConfigs = computed(() => {
let config: UpdatedConfigs[] = [ let config: UpdatedConfigs[] = [
{ {
@@ -209,23 +202,13 @@ export function useConfigHandler(updatedConfigs?: Config) {
{ {
name: 'GOOGLE_CLIENT_SECRET', name: 'GOOGLE_CLIENT_SECRET',
value: updatedConfigs?.providers.google.fields.client_secret ?? '', value: updatedConfigs?.providers.google.fields.client_secret ?? '',
},
{
name: 'GOOGLE_CALLBACK_URL',
value: updatedConfigs?.providers.google.fields.callback_url ?? '',
},
{
name: 'GOOGLE_SCOPE',
value: updatedConfigs?.providers.google.fields.scope ?? '',
} }
); );
} else { } else {
config = config.filter( config = config.filter(
(item) => (item) =>
item.name !== 'GOOGLE_CLIENT_ID' && item.name !== 'GOOGLE_CLIENT_ID' &&
item.name !== 'GOOGLE_CLIENT_SECRET' && item.name !== 'GOOGLE_CLIENT_SECRET'
item.name !== 'GOOGLE_CALLBACK_URL' &&
item.name !== 'GOOGLE_SCOPE'
); );
} }
if (updatedConfigs?.providers.microsoft.enabled) { if (updatedConfigs?.providers.microsoft.enabled) {
@@ -237,28 +220,13 @@ export function useConfigHandler(updatedConfigs?: Config) {
{ {
name: 'MICROSOFT_CLIENT_SECRET', name: 'MICROSOFT_CLIENT_SECRET',
value: updatedConfigs?.providers.microsoft.fields.client_secret ?? '', value: updatedConfigs?.providers.microsoft.fields.client_secret ?? '',
},
{
name: 'MICROSOFT_CALLBACK_URL',
value: updatedConfigs?.providers.microsoft.fields.callback_url ?? '',
},
{
name: 'MICROSOFT_SCOPE',
value: updatedConfigs?.providers.microsoft.fields.scope ?? '',
},
{
name: 'MICROSOFT_TENANT',
value: updatedConfigs?.providers.microsoft.fields.tenant ?? '',
} }
); );
} else { } else {
config = config.filter( config = config.filter(
(item) => (item) =>
item.name !== 'MICROSOFT_CLIENT_ID' && item.name !== 'MICROSOFT_CLIENT_ID' &&
item.name !== 'MICROSOFT_CLIENT_SECRET' && item.name !== 'MICROSOFT_CLIENT_SECRET'
item.name !== 'MICROSOFT_CALLBACK_URL' &&
item.name !== 'MICROSOFT_SCOPE' &&
item.name !== 'MICROSOFT_TENANT'
); );
} }
@@ -271,23 +239,13 @@ export function useConfigHandler(updatedConfigs?: Config) {
{ {
name: 'GITHUB_CLIENT_SECRET', name: 'GITHUB_CLIENT_SECRET',
value: updatedConfigs?.providers.github.fields.client_secret ?? '', value: updatedConfigs?.providers.github.fields.client_secret ?? '',
},
{
name: 'GITHUB_CALLBACK_URL',
value: updatedConfigs?.providers.github.fields.callback_url ?? '',
},
{
name: 'GITHUB_SCOPE',
value: updatedConfigs?.providers.github.fields.scope ?? '',
} }
); );
} else { } else {
config = config.filter( config = config.filter(
(item) => (item) =>
item.name !== 'GITHUB_CLIENT_ID' && item.name !== 'GITHUB_CLIENT_ID' &&
item.name !== 'GITHUB_CLIENT_SECRET' && item.name !== 'GITHUB_CLIENT_SECRET'
item.name !== 'GITHUB_CALLBACK_URL' &&
item.name !== 'GITHUB_SCOPE'
); );
} }

View File

@@ -1,4 +1,4 @@
import { onMounted, ref, Ref } from 'vue'; import { Ref, onMounted, ref } from 'vue';
import { DocumentNode } from 'graphql'; import { DocumentNode } from 'graphql';
import { TypedDocumentNode, useClientHandle } from '@urql/vue'; import { TypedDocumentNode, useClientHandle } from '@urql/vue';
@@ -9,9 +9,9 @@ export function usePagedQuery<
>( >(
query: string | TypedDocumentNode<Result, Vars> | DocumentNode, query: string | TypedDocumentNode<Result, Vars> | DocumentNode,
getList: (result: Result) => ListItem[], getList: (result: Result) => ListItem[],
getCursor: (value: ListItem) => string,
itemsPerPage: number, itemsPerPage: number,
baseVariables: Vars, variables: Vars
getCursor?: (value: ListItem) => string
) { ) {
const { client } = useClientHandle(); const { client } = useClientHandle();
const fetching = ref(true); const fetching = ref(true);
@@ -20,25 +20,21 @@ export function usePagedQuery<
const currentPage = ref(0); const currentPage = ref(0);
const hasNextPage = ref(true); const hasNextPage = ref(true);
const fetchNextPage = async (additionalVariables?: Vars) => { const fetchNextPage = async () => {
let variables = { ...baseVariables };
fetching.value = true; fetching.value = true;
// Cursor based pagination const cursor =
if (getCursor) { list.value.length > 0 ? getCursor(list.value.at(-1)!) : undefined;
const cursor = const variablesForPagination = {
list.value.length > 0 ...variables,
? getCursor(list.value.at(-1) as ListItem) take: itemsPerPage,
: undefined; cursor,
variables = { ...variables, cursor }; };
}
// Offset based pagination const result = await client
else if (additionalVariables) { .query(query, variablesForPagination)
variables = { ...variables, ...additionalVariables }; .toPromise();
}
const result = await client.query(query, variables).toPromise();
if (result.error) { if (result.error) {
error.value = true; error.value = true;
fetching.value = false; fetching.value = false;
@@ -67,14 +63,11 @@ export function usePagedQuery<
} }
}; };
const refetch = async (variables?: Vars) => { const refetch = async () => {
currentPage.value = 0; currentPage.value = 0;
hasNextPage.value = true; hasNextPage.value = true;
list.value = []; list.value = [];
await fetchNextPage();
if (hasNextPage.value) {
variables ? await fetchNextPage(variables) : await fetchNextPage();
}
}; };
return { return {

View File

@@ -1,3 +0,0 @@
mutation DemoteUsersByAdmin($userUIDs: [ID!]!) {
demoteUsersByAdmin(userUIDs: $userUIDs)
}

View File

@@ -0,0 +1,3 @@
mutation MakeUserAdmin($uid: ID!) {
makeUserAdmin(userUID: $uid)
}

View File

@@ -1,3 +0,0 @@
mutation MakeUsersAdmin($userUIDs: [ID!]!) {
makeUsersAdmin(userUIDs: $userUIDs)
}

View File

@@ -0,0 +1,3 @@
mutation RemoveUserAsAdmin($uid: ID!) {
removeUserAsAdmin(userUID: $uid)
}

View File

@@ -0,0 +1,3 @@
mutation RemoveUserByAdmin($uid: ID!) {
removeUserByAdmin(userUID: $uid)
}

View File

@@ -1,7 +0,0 @@
mutation RemoveUsersByAdmin($userUIDs: [ID!]!) {
removeUsersByAdmin(userUIDs: $userUIDs) {
userUID
isDeleted
errorMessage
}
}

View File

@@ -1,3 +0,0 @@
mutation RevokeUserInvitationsByAdmin($inviteeEmails: [String!]!) {
revokeUserInvitationsByAdmin(inviteeEmails: $inviteeEmails)
}

View File

@@ -1,3 +0,0 @@
mutation UpdateUserDisplayNameByAdmin($userUID: ID!, $name: String!) {
updateUserDisplayNameByAdmin(userUID: $userUID, displayName: $name)
}

View File

@@ -1,12 +0,0 @@
query UsersListV2($searchString: String, $skip: Int, $take: Int) {
infra {
allUsersV2(searchString: $searchString, skip: $skip, take: $take) {
uid
displayName
email
isAdmin
photoURL
createdOn
}
}
}

View File

@@ -7,13 +7,3 @@ export const UNAUTHORIZED = 'Unauthorized' as const;
// Sometimes the backend returns Unauthorized error message as follows: // Sometimes the backend returns Unauthorized error message as follows:
export const GRAPHQL_UNAUTHORIZED = '[GraphQL] Unauthorized' as const; export const GRAPHQL_UNAUTHORIZED = '[GraphQL] Unauthorized' as const;
export const DELETE_USER_FAILED_ONLY_ONE_ADMIN =
'admin/only_one_admin_account_found' as const;
export const ADMIN_CANNOT_BE_DELETED =
'admin/admin_can_not_be_deleted' as const;
// When trying to invite a user that is already invited
export const USER_ALREADY_INVITED =
'[GraphQL] admin/user_already_invited' as const;

View File

@@ -18,83 +18,89 @@
<div v-else-if="error">{{ t('teams.load_list_error') }}</div> <div v-else-if="error">{{ t('teams.load_list_error') }}</div>
<HoppSmartTable <HoppSmartTable v-else-if="teamsList.length" :list="teamsList">
v-else-if="teamsList.length"
:headings="headings"
:list="teamsList"
@onRowClicked="goToTeamDetails"
>
<template #head> <template #head>
<th class="px-6 py-2">{{ t('teams.id') }}</th> <tr
<th class="px-6 py-2">{{ t('teams.name') }}</th> class="text-secondary border-b border-dividerDark text-sm text-left bg-primaryLight"
<th class="px-6 py-2">{{ t('teams.members') }}</th> >
<!-- Empty Heading for the Action Button --> <th class="px-6 py-2">{{ t('teams.id') }}</th>
<th class="px-6 py-2"></th> <th class="px-6 py-2">{{ t('teams.name') }}</th>
<th class="px-6 py-2">{{ t('teams.members') }}</th>
<!-- Empty Heading for the Action Button -->
<th class="px-6 py-2"></th>
</tr>
</template> </template>
<template #body="{ row: team }"> <template #body="{ list }">
<td class="flex py-4 px-7 max-w-[16rem]"> <tr
<span class="truncate"> v-for="team in list"
{{ team.id }} :key="team.id"
</span> class="text-secondaryDark hover:bg-divider hover:cursor-pointer rounded-xl"
</td> @click="goToTeamDetails(team.id)"
>
<td class="flex py-4 px-7 max-w-[16rem]">
<span class="truncate">
{{ team.id }}
</span>
</td>
<td class="py-4 px-7 min-w-[20rem]"> <td class="py-4 px-7 min-w-[20rem]">
<span <span
class="flex items-center truncate" class="flex items-center truncate"
:class="{ truncate: team.name }" :class="{ truncate: team.name }"
> >
{{ team.name ?? t('teams.unnamed') }} {{ team.name ?? t('teams.unnamed') }}
</span> </span>
</td> </td>
<td class="py-4 px-8"> <td class="py-4 px-7">
{{ team.members?.length }} {{ team.members?.length }}
</td> </td>
<td @click.stop class="flex justify-end mr-10"> <td @click.stop>
<div class="relative"> <div class="relative">
<tippy interactive trigger="click" theme="popover"> <tippy interactive trigger="click" theme="popover">
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:icon="IconMoreHorizontal" :icon="IconMoreHorizontal"
/> />
<template #content="{ hide }"> <template #content="{ hide }">
<div <div
ref="tippyActions" ref="tippyActions"
class="flex flex-col focus:outline-none" class="flex flex-col focus:outline-none"
tabindex="0" tabindex="0"
@keyup.escape="hide()" @keyup.escape="hide()"
> >
<HoppSmartItem <HoppSmartItem
:icon="IconTrash" :icon="IconTrash"
:label="t('teams.delete_team')" :label="t('teams.delete_team')"
class="!hover:bg-red-600 w-full" class="!hover:bg-red-600 w-full"
@click=" @click="
() => { () => {
deleteTeam(team.id); deleteTeam(team.id);
hide(); hide();
} }
" "
/> />
</div> </div>
</template> </template>
</tippy> </tippy>
</div> </div>
</td> </td>
</tr>
</template> </template>
</HoppSmartTable> </HoppSmartTable>
<div v-else class="px-2"> <div v-else class="px-2 text-lg">
{{ t('teams.no_teams') }} {{ t('teams.no_teams') }}
</div> </div>
<div <div
v-if="hasNextPage && teamsList.length >= teamsPerPage" v-if="hasNextPage && teamsList.length >= teamsPerPage"
class="flex items-center w-28 px-3 py-2 mt-5 mx-auto font-semibold text-secondaryDark bg-divider hover:bg-dividerDark rounded-3xl cursor-pointer" class="flex justify-center my-5 px-3 py-2 cursor-pointer font-semibold rounded-3xl bg-dividerDark hover:bg-divider transition mx-auto w-38 text-secondaryDark"
@click="fetchNextTeams" @click="fetchNextTeams"
> >
<span>{{ t('teams.show_more') }}</span> <span>{{ t('teams.show_more') }}</span>
<icon-lucide-chevron-down class="ml-2" /> <icon-lucide-chevron-down class="ml-2 text-lg" />
</div> </div>
</div> </div>
</div> </div>
@@ -129,7 +135,6 @@ import {
CreateTeamDocument, CreateTeamDocument,
MetricsDocument, MetricsDocument,
RemoveTeamDocument, RemoveTeamDocument,
TeamInfoQuery,
TeamListDocument, TeamListDocument,
UsersListDocument, UsersListDocument,
} from '../../helpers/backend/graphql'; } from '../../helpers/backend/graphql';
@@ -144,9 +149,9 @@ const usersPerPage = computed(() => data.value?.infra.usersCount || 10000);
const { list: usersList } = usePagedQuery( const { list: usersList } = usePagedQuery(
UsersListDocument, UsersListDocument,
(x) => x.infra.allUsers, (x) => x.infra.allUsers,
(x) => x.uid,
usersPerPage.value, usersPerPage.value,
{ cursor: undefined, take: usersPerPage.value }, { cursor: undefined, take: usersPerPage.value }
(x) => x.uid
); );
const allUsersEmail = computed(() => usersList.value.map((user) => user.email)); const allUsersEmail = computed(() => usersList.value.map((user) => user.email));
@@ -163,19 +168,11 @@ const {
} = usePagedQuery( } = usePagedQuery(
TeamListDocument, TeamListDocument,
(x) => x.infra.allTeams, (x) => x.infra.allTeams,
(x) => x.id,
teamsPerPage, teamsPerPage,
{ cursor: undefined, take: teamsPerPage }, { cursor: undefined, take: teamsPerPage }
(x) => x.id
); );
// Table Headings
const headings = [
{ key: 'id', label: t('teams.id') },
{ key: 'name', label: t('teams.name') },
{ key: 'members', label: t('teams.members') },
{ key: 'actions', label: '' },
];
// Create Team // Create Team
const showCreateTeamModal = ref(false); const showCreateTeamModal = ref(false);
const createTeamLoading = ref(false); const createTeamLoading = ref(false);
@@ -215,8 +212,7 @@ const createTeam = async (newTeamName: string, ownerEmail: string) => {
// Go To Individual Team Details Page // Go To Individual Team Details Page
const router = useRouter(); const router = useRouter();
const goToTeamDetails = (team: TeamInfoQuery['infra']['teamInfo']) => const goToTeamDetails = (teamId: string) => router.push('/teams/' + teamId);
router.push('/teams/' + team.id);
// Team Deletion // Team Deletion
const confirmDeletion = ref(false); const confirmDeletion = ref(false);

View File

@@ -12,7 +12,7 @@
<div class="flex items-center space-x-3"> <div class="flex items-center space-x-3">
<h1 class="text-lg text-accentContrast"> <h1 class="text-lg text-accentContrast">
{{ userName }} {{ user.displayName }}
</h1> </h1>
<span>/</span> <span>/</span>
<h2 class="text-lg text-accentContrast"> <h2 class="text-lg text-accentContrast">
@@ -29,7 +29,6 @@
@delete-user="deleteUser" @delete-user="deleteUser"
@make-admin="makeUserAdmin" @make-admin="makeUserAdmin"
@remove-admin="makeAdminToUser" @remove-admin="makeAdminToUser"
@update-user-name="(name: string) => (userName = name)"
class="py-8 px-4" class="py-8 px-4"
/> />
</HoppSmartTab> </HoppSmartTab>
@@ -41,19 +40,19 @@
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmDeletion" :show="confirmDeletion"
:title="t('state.confirm_user_deletion')" :title="t('users.confirm_user_deletion')"
@hide-modal="confirmDeletion = false" @hide-modal="confirmDeletion = false"
@resolve="deleteUserMutation(deleteUserUID)" @resolve="deleteUserMutation(deleteUserUID)"
/> />
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmUserToAdmin" :show="confirmUserToAdmin"
:title="t('state.confirm_user_to_admin')" :title="t('users.confirm_user_to_admin')"
@hide-modal="confirmUserToAdmin = false" @hide-modal="confirmUserToAdmin = false"
@resolve="makeUserAdminMutation(userToAdminUID)" @resolve="makeUserAdminMutation(userToAdminUID)"
/> />
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmAdminToUser" :show="confirmAdminToUser"
:title="t('state.confirm_admin_to_user')" :title="t('users.confirm_admin_to_user')"
@hide-modal="confirmAdminToUser = false" @hide-modal="confirmAdminToUser = false"
@resolve="makeAdminToUserMutation(adminToUserUID)" @resolve="makeAdminToUserMutation(adminToUserUID)"
/> />
@@ -68,12 +67,11 @@ import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast'; import { useToast } from '~/composables/toast';
import { useClientHandler } from '~/composables/useClientHandler'; import { useClientHandler } from '~/composables/useClientHandler';
import { import {
DemoteUsersByAdminDocument, MakeUserAdminDocument,
MakeUsersAdminDocument, RemoveUserAsAdminDocument,
RemoveUsersByAdminDocument, RemoveUserByAdminDocument,
UserInfoDocument, UserInfoDocument,
} from '~/helpers/backend/graphql'; } from '~/helpers/backend/graphql';
import { ADMIN_CANNOT_BE_DELETED } from '~/helpers/errors';
const t = useI18n(); const t = useI18n();
const toast = useToast(); const toast = useToast();
@@ -106,15 +104,6 @@ onMounted(async () => {
await fetchData(); await fetchData();
}); });
const userName = computed({
get: () => data.value?.infra.userInfo.displayName,
set: (value) => {
if (value) {
data.value!.infra.userInfo.displayName = value;
}
},
});
const user = computed({ const user = computed({
get: () => data.value?.infra.userInfo, get: () => data.value?.infra.userInfo,
set: (value) => { set: (value) => {
@@ -124,69 +113,9 @@ const user = computed({
}, },
}); });
const confirmUserToAdmin = ref(false);
const userToAdminUID = ref<string | null>(null);
const usersToAdmin = useMutation(MakeUsersAdminDocument);
const makeUserAdmin = (id: string | null) => {
confirmUserToAdmin.value = true;
userToAdminUID.value = id;
};
const makeUserAdminMutation = async (id: string | null) => {
if (!id) {
confirmUserToAdmin.value = false;
toast.error(t('state.admin_failure'));
return;
}
const userUIDs = [id];
const variables = { userUIDs };
const result = await usersToAdmin.executeMutation(variables);
if (result.error) {
toast.error(t('state.admin_failure'));
} else {
toast.success(t('state.admin_success'));
user.value!.isAdmin = true;
}
confirmUserToAdmin.value = false;
userToAdminUID.value = null;
};
// Remove Admin Status from a current admin user
const adminToUser = useMutation(DemoteUsersByAdminDocument);
const confirmAdminToUser = ref(false);
const adminToUserUID = ref<string | null>(null);
const makeAdminToUser = (id: string) => {
confirmAdminToUser.value = true;
adminToUserUID.value = id;
};
const makeAdminToUserMutation = async (id: string | null) => {
if (!id) {
confirmAdminToUser.value = false;
toast.error(t('state.remove_admin_failure'));
return;
}
const userUIDs = [id];
const variables = { userUIDs };
const result = await adminToUser.executeMutation(variables);
if (result.error) {
toast.error(t('state.remove_admin_failure'));
} else {
toast.success(t('state.remove_admin_success'));
user.value!.isAdmin = false;
}
confirmAdminToUser.value = false;
adminToUserUID.value = null;
};
// User Deletion // User Deletion
const router = useRouter(); const router = useRouter();
const userDeletion = useMutation(RemoveUsersByAdminDocument); const userDeletion = useMutation(RemoveUserByAdminDocument);
const confirmDeletion = ref(false); const confirmDeletion = ref(false);
const deleteUserUID = ref<string | null>(null); const deleteUserUID = ref<string | null>(null);
@@ -201,25 +130,73 @@ const deleteUserMutation = async (id: string | null) => {
toast.error(t('state.delete_user_failure')); toast.error(t('state.delete_user_failure'));
return; return;
} }
const userUIDs = [id]; const variables = { uid: id };
const variables = { userUIDs };
const result = await userDeletion.executeMutation(variables); const result = await userDeletion.executeMutation(variables);
if (result.error) { if (result.error) {
toast.error(t('state.delete_user_failure')); toast.error(t('state.delete_user_failure'));
} else { } else {
const deletedUsers = result.data?.removeUsersByAdmin || []; toast.success(t('state.delete_user_success'));
const isAdminError = deletedUsers.some(
(user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED
);
isAdminError
? toast.error(t('state.delete_user_failed_only_one_admin'))
: toast.success(t('state.delete_user_success'));
} }
confirmDeletion.value = false; confirmDeletion.value = false;
deleteUserUID.value = null; deleteUserUID.value = null;
router.push('/users'); router.push('/users');
}; };
// Make User Admin
const userToAdmin = useMutation(MakeUserAdminDocument);
const confirmUserToAdmin = ref(false);
const userToAdminUID = ref<string | null>(null);
const makeUserAdmin = (id: string) => {
confirmUserToAdmin.value = true;
userToAdminUID.value = id;
};
const makeUserAdminMutation = async (id: string | null) => {
if (!id) {
confirmUserToAdmin.value = false;
toast.error(t('state.admin_failure'));
return;
}
const variables = { uid: id };
const result = await userToAdmin.executeMutation(variables);
if (result.error) {
toast.error(t('state.admin_failure'));
} else {
user.value!.isAdmin = true;
toast.success(t('state.admin_success'));
}
confirmUserToAdmin.value = false;
userToAdminUID.value = null;
};
// Remove Admin Status from a current admin user
const adminToUser = useMutation(RemoveUserAsAdminDocument);
const confirmAdminToUser = ref(false);
const adminToUserUID = ref<string | null>(null);
const makeAdminToUser = (id: string) => {
confirmAdminToUser.value = true;
adminToUserUID.value = id;
};
const makeAdminToUserMutation = async (id: string | null) => {
if (!id) {
confirmAdminToUser.value = false;
toast.error(t('state.remove_admin_failure'));
return;
}
const variables = { uid: id };
const result = await adminToUser.executeMutation(variables);
if (result.error) {
toast.error(t('state.remove_admin_failure'));
} else {
user.value!.isAdmin = false;
toast.error(t('state.remove_admin_success'));
}
confirmAdminToUser.value = false;
adminToUserUID.value = null;
};
</script> </script>

View File

@@ -5,7 +5,7 @@
<h1 class="text-lg font-bold text-secondaryDark"> <h1 class="text-lg font-bold text-secondaryDark">
{{ t('users.users') }} {{ t('users.users') }}
</h1> </h1>
<div class="flex items-center space-x-4 mt-10 mb-5"> <div class="flex items-center space-x-4 py-10">
<HoppButtonPrimary <HoppButtonPrimary
:label="t('users.invite_user')" :label="t('users.invite_user')"
@click="showInviteUserModal = true" @click="showInviteUserModal = true"
@@ -15,189 +15,132 @@
<HoppButtonSecondary <HoppButtonSecondary
outline outline
filled filled
:label="t('users.pending_invites')" :label="t('users.invited_users')"
:to="'/users/invited'" :to="'/users/invited'"
/> />
</div> </div>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<div class="mb-3 flex items-center justify-end"> <div v-if="fetching" class="flex justify-center">
<HoppButtonSecondary <HoppSmartSpinner />
outline
filled
:icon="IconLeft"
:disabled="page === 1"
@click="changePage(PageDirection.Previous)"
/>
<div class="flex h-full w-10 items-center justify-center">
<span>{{ page }}</span>
</div>
<HoppButtonSecondary
outline
filled
:icon="IconRight"
:disabled="page >= totalPages"
@click="changePage(PageDirection.Next)"
/>
</div> </div>
<HoppSmartTable <div v-else-if="error">{{ t('users.load_list_error') }}</div>
v-model:list="finalUsersList"
v-model:selected-rows="selectedRows" <HoppSmartTable v-else-if="usersList.length" :list="usersList">
:headings="headings"
:loading="showSpinner"
@onRowClicked="goToUserDetails"
>
<template #extension>
<div class="flex w-full items-center bg-primary">
<icon-lucide-search class="mx-3 text-xs" />
<HoppSmartInput
v-model="query"
styles="w-full bg-primary py-1"
input-styles="h-full border-none"
:placeholder="t('users.searchbar_placeholder')"
/>
</div>
</template>
<template #head> <template #head>
<th class="px-6 py-2">{{ t('users.id') }}</th> <tr
<th class="px-6 py-2">{{ t('users.name') }}</th> class="text-secondary border-b border-dividerDark text-sm text-left bg-primaryLight"
<th class="px-6 py-2">{{ t('users.email') }}</th> >
<th class="px-6 py-2">{{ t('users.date') }}</th> <th class="px-6 py-2">{{ t('users.id') }}</th>
<!-- Empty header for Action Button --> <th class="px-6 py-2">{{ t('users.name') }}</th>
<th class="w-20 px-6 py-2"></th> <th class="px-6 py-2">{{ t('users.email') }}</th>
<th class="px-6 py-2">{{ t('users.date') }}</th>
<!-- Empty header for Action Button -->
<th class="px-6 py-2"></th>
</tr>
</template> </template>
<template #empty-state> <template #body="{ list }">
<td colspan="6"> <tr
<span class="flex justify-center p-3"> v-for="user in list"
{{ error ? t('users.load_list_error') : t('users.no_users') }} :key="user.uid"
</span> class="text-secondaryDark hover:bg-divider hover:cursor-pointer rounded-xl"
</td> @click="goToUserDetails(user.uid)"
</template> >
<td class="py-2 px-7 max-w-[8rem] truncate">
{{ user.uid }}
</td>
<template #body="{ row: user }"> <td class="py-2 px-7">
<td class="py-2 px-7 max-w-[8rem] truncate"> <div class="flex items-center space-x-2">
{{ user.uid }} <span>
</td> {{ user.displayName ?? t('users.unnamed') }}
</span>
<td class="py-2 px-7"> <span
<div class="flex items-center space-x-2"> v-if="user.isAdmin"
<span> class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
{{ user.displayName ?? t('users.unnamed') }} >
</span> {{ t('users.admin') }}
</span>
</div>
</td>
<span <td class="py-2 px-7">
v-if="user.isAdmin" {{ user.email }}
class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300" </td>
>
{{ t('users.admin') }}
</span>
</div>
</td>
<td class="py-2 px-7 truncate"> <td class="py-2 px-7">
{{ user.email }} {{ getCreatedDate(user.createdOn) }}
</td> <div class="text-gray-400 text-tiny">
{{ getCreatedTime(user.createdOn) }}
</div>
</td>
<td class="py-2 px-7"> <td @click.stop>
{{ getCreatedDate(user.createdOn) }} <div class="relative">
<div class="text-gray-400 text-tiny"> <tippy interactive trigger="click" theme="popover">
{{ getCreatedTime(user.createdOn) }} <HoppButtonSecondary
</div> v-tippy="{ theme: 'tooltip' }"
</td> :icon="IconMoreHorizontal"
/>
<td @click.stop class="flex justify-end w-20"> <template #content="{ hide }">
<div class="mt-2 mr-5"> <div
<tippy interactive trigger="click" theme="popover"> ref="tippyActions"
<HoppButtonSecondary class="flex flex-col focus:outline-none"
v-tippy="{ theme: 'tooltip' }" tabindex="0"
:icon="IconMoreHorizontal" @keyup.escape="hide()"
/> >
<template #content="{ hide }"> <HoppSmartItem
<div :icon="user.isAdmin ? IconUserMinus : IconUserCheck"
ref="tippyActions" :label="
class="flex flex-col focus:outline-none"
tabindex="0"
@keyup.escape="hide()"
>
<HoppSmartItem
:icon="user.isAdmin ? IconUserMinus : IconUserCheck"
:label="
user.isAdmin
? t('users.remove_admin_status')
: t('users.make_admin')
"
class="!hover:bg-emerald-600"
@click="
() => {
user.isAdmin user.isAdmin
? confirmAdminToUser(user.uid) ? t('users.remove_admin_status')
: confirmUserToAdmin(user.uid); : t('users.make_admin')
hide(); "
} class="!hover:bg-emerald-600"
" @click="
/> () => {
<HoppSmartItem user.isAdmin
v-if="!user.isAdmin" ? makeAdminToUser(user.uid)
:icon="IconTrash" : makeUserAdmin(user.uid);
:label="t('users.delete_user')" hide();
class="!hover:bg-red-600" }
@click=" "
() => { />
confirmUserDeletion(user.uid); <HoppSmartItem
hide(); v-if="!user.isAdmin"
} :icon="IconTrash"
" :label="t('users.delete_user')"
/> class="!hover:bg-red-600"
</div> @click="
</template> () => {
</tippy> deleteUser(user.uid);
</div> hide();
</td> }
"
/>
</div>
</template>
</tippy>
</div>
</td>
</tr>
</template> </template>
</HoppSmartTable> </HoppSmartTable>
<!-- Actions for Selected Rows --> <div v-else-if="usersList.length === 0" class="flex justify-center">
{{ t('users.no_users') }}
</div>
<div <div
v-if="selectedRows.length" v-if="hasNextPage && usersList.length >= usersPerPage"
class="fixed m-2 bottom-0 left-40 right-0 w-min mx-auto shadow-2xl" class="flex justify-center my-5 px-3 py-2 cursor-pointer font-semibold rounded-3xl bg-dividerDark hover:bg-divider transition mx-auto w-38 text-secondaryDark"
@click="fetchNextUsers"
> >
<div <span>{{ t('users.show_more') }}</span>
class="flex justify-center items-end bg-primaryLight border border-divider rounded-md mb-5" <icon-lucide-chevron-down class="ml-2 text-lg" />
>
<HoppButtonSecondary
:icon="IconCheck"
:label="t('state.selected', { count: selectedRows.length })"
class="py-4 border-divider rounded-r-none bg-emerald-800 text-secondaryDark"
/>
<HoppButtonSecondary
:icon="IconUserCheck"
:label="t('users.make_admin')"
class="py-4 border-divider border-r-1 rounded-none hover:bg-emerald-600"
@click="confirmUsersToAdmin = true"
/>
<HoppButtonSecondary
:icon="IconUserMinus"
:label="t('users.remove_admin_status')"
class="py-4 border-divider border-r-1 rounded-none hover:bg-orange-500"
@click="confirmAdminsToUsers = true"
/>
<HoppButtonSecondary
:icon="IconTrash"
:label="t('users.delete_users')"
class="py-4 border-divider rounded-none hover:bg-red-500"
@click="confirmUsersDeletion = true"
/>
<HoppButtonSecondary
:icon="IconX"
:label="t('state.clear_selection')"
class="py-4 border-divider rounded-l-none text-secondaryDark bg-red-600 hover:bg-red-500"
@click="selectedRows.splice(0, selectedRows.length)"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -208,70 +151,46 @@
@send-invite="sendInvite" @send-invite="sendInvite"
/> />
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmUsersToAdmin" :show="confirmDeletion"
:title=" :title="t('users.confirm_user_deletion')"
AreMultipleUsersSelected @hide-modal="confirmDeletion = false"
? t('state.confirm_users_to_admin') @resolve="deleteUserMutation(deleteUserUID)"
: t('state.confirm_user_to_admin')
"
@hide-modal="resetConfirmUserToAdmin"
@resolve="makeUsersToAdmin(usersToAdminUID)"
/> />
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmAdminsToUsers" :show="confirmUserToAdmin"
:title=" :title="t('users.confirm_user_to_admin')"
AreMultipleUsersSelectedToAdmin @hide-modal="confirmUserToAdmin = false"
? t('state.confirm_admins_to_users') @resolve="makeUserAdminMutation(userToAdminUID)"
: t('state.confirm_admin_to_user')
"
@hide-modal="resetConfirmAdminToUser"
@resolve="makeAdminsToUsers(adminsToUserUID)"
/> />
<HoppSmartConfirmModal <HoppSmartConfirmModal
:show="confirmUsersDeletion" :show="confirmAdminToUser"
:title=" :title="t('users.confirm_admin_to_user')"
AreMultipleUsersSelectedForDeletion @hide-modal="confirmAdminToUser = false"
? t('state.confirm_users_deletion') @resolve="makeAdminToUserMutation(adminToUserUID)"
: t('state.confirm_user_deletion')
"
@hide-modal="resetConfirmUserDeletion"
@resolve="deleteUsers(deleteUserUID)"
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useMutation, useQuery } from '@urql/vue'; import { useMutation } from '@urql/vue';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { computed, onUnmounted, ref, watch } from 'vue'; import { ref } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast'; import { useToast } from '~/composables/toast';
import { usePagedQuery } from '~/composables/usePagedQuery'; import { usePagedQuery } from '~/composables/usePagedQuery';
import {
DemoteUsersByAdminDocument,
InviteNewUserDocument,
MakeUsersAdminDocument,
MetricsDocument,
RemoveUsersByAdminDocument,
UserInfoQuery,
UsersListQuery,
UsersListV2Document,
} from '~/helpers/backend/graphql';
import {
ADMIN_CANNOT_BE_DELETED,
DELETE_USER_FAILED_ONLY_ONE_ADMIN,
USER_ALREADY_INVITED,
} from '~/helpers/errors';
import IconCheck from '~icons/lucide/check';
import IconLeft from '~icons/lucide/chevron-left';
import IconRight from '~icons/lucide/chevron-right';
import IconMoreHorizontal from '~icons/lucide/more-horizontal'; import IconMoreHorizontal from '~icons/lucide/more-horizontal';
import IconTrash from '~icons/lucide/trash'; import IconTrash from '~icons/lucide/trash';
import IconUserCheck from '~icons/lucide/user-check'; import IconUserCheck from '~icons/lucide/user-check';
import IconUserMinus from '~icons/lucide/user-minus'; import IconUserMinus from '~icons/lucide/user-minus';
import IconAddUser from '~icons/lucide/user-plus'; import IconAddUser from '~icons/lucide/user-plus';
import IconX from '~icons/lucide/x'; import {
InviteNewUserDocument,
MakeUserAdminDocument,
RemoveUserAsAdminDocument,
RemoveUserByAdminDocument,
UsersListDocument,
} from '~/helpers/backend/graphql';
// Get Proper Date Formats // Get Proper Date Formats
const t = useI18n(); const t = useI18n();
@@ -280,165 +199,25 @@ const toast = useToast();
const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy'); const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy');
const getCreatedTime = (date: string) => format(new Date(date), 'hh:mm a'); const getCreatedTime = (date: string) => format(new Date(date), 'hh:mm a');
// Table Headings
const headings = [
{ key: 'uid', label: t('users.id') },
{ key: 'displayName', label: t('users.name') },
{ key: 'email', label: t('users.email') },
{ key: 'createdOn', label: t('users.date') },
{ key: '', label: '' },
];
// Get Paginated Results of all the users in the infra // Get Paginated Results of all the users in the infra
const usersPerPage = 20; const usersPerPage = 20;
const { const {
fetching, fetching,
error, error,
refetch, goToNextPage: fetchNextUsers,
list: usersList, list: usersList,
hasNextPage,
} = usePagedQuery( } = usePagedQuery(
UsersListV2Document, UsersListDocument,
(x) => x.infra.allUsersV2, (x) => x.infra.allUsers,
(x) => x.uid,
usersPerPage, usersPerPage,
{ searchString: '', take: usersPerPage, skip: 0 } { cursor: undefined, take: usersPerPage }
); );
// Selected Rows
const selectedRows = ref<UsersListQuery['infra']['allUsers']>([]);
// Ensure this variable is declared outside the debounce function
let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
let toastTimeout: ReturnType<typeof setTimeout> | null = null;
onUnmounted(() => {
if (debounceTimeout) {
clearTimeout(debounceTimeout);
}
if (toastTimeout) {
clearTimeout(toastTimeout);
}
});
// Debounce Function
const debounce = (func: () => void, delay: number) => {
if (debounceTimeout) clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(func, delay);
};
// Search
const query = ref('');
// Query which is sent to the backend after debouncing
const searchQuery = ref('');
const handleSearch = async (input: string) => {
searchQuery.value = input;
if (input.length === 0) {
await refetch({
searchString: '',
take: usersPerPage,
skip: (page.value - 1) * usersPerPage,
});
} else {
// If search query is present, fetch all the users filtered by the search query
await refetch({ searchString: input, take: usersCount.value!, skip: 0 });
}
// Reset the page to 1 when the search query changes
page.value = 1;
};
watch(query, () => {
if (query.value.length === 0) {
handleSearch(query.value);
} else {
debounce(() => {
handleSearch(query.value);
}, 500);
}
});
// Final Users List after Search and Pagination operations
const finalUsersList = computed(() =>
// If search query is present, filter the list based on the search query and return the paginated results
// Else just return the paginated results directly
searchQuery.value.length > 0
? usersList.value.slice(
(page.value - 1) * usersPerPage,
page.value * usersPerPage
)
: usersList.value
);
// Spinner
const showSpinner = ref(false);
watch(fetching, (fetching) => {
if (fetching) {
showSpinner.value = true;
debounce(() => {
showSpinner.value = false;
}, 500);
}
});
// Pagination
enum PageDirection {
Previous,
Next,
}
const page = ref(1);
const { data } = useQuery({ query: MetricsDocument });
const usersCount = computed(() => data?.value?.infra.usersCount);
const changePage = (direction: PageDirection) => {
const isPrevious = direction === PageDirection.Previous;
const isValidPreviousAction = isPrevious && page.value > 1;
const isValidNextAction = !isPrevious && page.value < totalPages.value;
if (isValidNextAction || isValidPreviousAction) {
page.value += isPrevious ? -1 : 1;
}
};
const totalPages = computed(() => {
if (!usersCount.value) return 0;
if (query.value.length > 0) {
return Math.ceil(usersList.value.length / usersPerPage);
}
return Math.ceil(usersCount.value / usersPerPage);
});
watch(page, async () => {
if (page.value < 1 || page.value > totalPages.value) {
return;
}
// Show spinner when moving to a different page when search query is present
else if (query.value.length > 0) {
showSpinner.value = true;
debounce(() => (showSpinner.value = false), 500);
} else {
await refetch({
searchString: '',
take: usersPerPage,
skip: (page.value - 1) * usersPerPage,
});
}
});
// Go to Individual User Details Page
const router = useRouter();
const goToUserDetails = (user: UserInfoQuery['infra']['userInfo']) =>
router.push('/users/' + user.uid);
// Send Invitation through Email // Send Invitation through Email
const showInviteUserModal = ref(false);
const sendInvitation = useMutation(InviteNewUserDocument); const sendInvitation = useMutation(InviteNewUserDocument);
const showInviteUserModal = ref(false);
const sendInvite = async (email: string) => { const sendInvite = async (email: string) => {
if (!email.trim()) { if (!email.trim()) {
@@ -448,172 +227,104 @@ const sendInvite = async (email: string) => {
const variables = { inviteeEmail: email.trim() }; const variables = { inviteeEmail: email.trim() };
const result = await sendInvitation.executeMutation(variables); const result = await sendInvitation.executeMutation(variables);
if (result.error) { if (result.error) {
if (result.error.message === USER_ALREADY_INVITED) toast.error(t('state.email_failure'));
toast.error(t('state.user_already_invited'));
else toast.error(t('state.email_failure'));
} else { } else {
toast.success(t('state.email_success')); toast.success(t('state.email_success'));
showInviteUserModal.value = false; showInviteUserModal.value = false;
} }
}; };
// Make Multiple Users Admin // Go to Individual User Details Page
const confirmUsersToAdmin = ref(false); const router = useRouter();
const usersToAdminUID = ref<string | null>(null); const goToUserDetails = (uid: string) => router.push('/users/' + uid);
const usersToAdmin = useMutation(MakeUsersAdminDocument);
const AreMultipleUsersSelected = computed(() => selectedRows.value.length > 1); // User Deletion
const userDeletion = useMutation(RemoveUserByAdminDocument);
const confirmUserToAdmin = (id: string | null) => { const confirmDeletion = ref(false);
confirmUsersToAdmin.value = true;
usersToAdminUID.value = id;
};
// Resets variables if user cancels the confirmation
const resetConfirmUserToAdmin = () => {
confirmUsersToAdmin.value = false;
usersToAdminUID.value = null;
};
const makeUsersToAdmin = async (id: string | null) => {
const userUIDs = id ? [id] : selectedRows.value.map((user) => user.uid);
const variables = { userUIDs };
const result = await usersToAdmin.executeMutation(variables);
if (result.error) {
toast.error(
id ? t('state.admin_failure') : t('state.users_to_admin_failure')
);
} else {
toast.success(
id ? t('state.admin_success') : t('state.users_to_admin_success')
);
usersList.value = usersList.value.map((user) => ({
...user,
isAdmin: userUIDs.includes(user.uid) ? true : user.isAdmin,
}));
selectedRows.value.splice(0, selectedRows.value.length);
}
confirmUsersToAdmin.value = false;
usersToAdminUID.value = null;
};
// Remove Admin Status from Multiple Users
const confirmAdminsToUsers = ref(false);
const adminsToUserUID = ref<string | null>(null);
const adminsToUser = useMutation(DemoteUsersByAdminDocument);
const confirmAdminToUser = (id: string | null) => {
confirmAdminsToUsers.value = true;
adminsToUserUID.value = id;
};
// Resets variables if user cancels the confirmation
const resetConfirmAdminToUser = () => {
confirmAdminsToUsers.value = false;
adminsToUserUID.value = null;
};
const AreMultipleUsersSelectedToAdmin = computed(
() => selectedRows.value.length > 1
);
const makeAdminsToUsers = async (id: string | null) => {
const userUIDs = id ? [id] : selectedRows.value.map((user) => user.uid);
const variables = { userUIDs };
const result = await adminsToUser.executeMutation(variables);
if (result.error) {
toast.error(
id
? t('state.remove_admin_failure')
: t('state.remove_admin_from_users_failure')
);
} else {
toast.success(
id
? t('state.remove_admin_success')
: t('state.remove_admin_from_users_success')
);
usersList.value = usersList.value.map((user) => ({
...user,
isAdmin: userUIDs.includes(user.uid) ? false : user.isAdmin,
}));
selectedRows.value.splice(0, selectedRows.value.length);
}
confirmAdminsToUsers.value = false;
adminsToUserUID.value = null;
};
// Delete Multiple Users
const confirmUsersDeletion = ref(false);
const deleteUserUID = ref<string | null>(null); const deleteUserUID = ref<string | null>(null);
const usersDeletion = useMutation(RemoveUsersByAdminDocument);
const confirmUserDeletion = (id: string | null) => { const deleteUserMutation = async (id: string | null) => {
confirmUsersDeletion.value = true; if (!id) {
confirmDeletion.value = false;
toast.error(t('state.delete_user_failure'));
return;
}
const variables = { uid: id };
const result = await userDeletion.executeMutation(variables);
if (result.error) {
toast.error(t('state.delete_user_failure'));
} else {
toast.success(t('state.delete_user_success'));
usersList.value = usersList.value.filter((user) => user.uid !== id);
}
confirmDeletion.value = false;
deleteUserUID.value = null;
};
// Make User Admin
const userToAdmin = useMutation(MakeUserAdminDocument);
const confirmUserToAdmin = ref(false);
const userToAdminUID = ref<string | null>(null);
const makeUserAdmin = (id: string) => {
confirmUserToAdmin.value = true;
userToAdminUID.value = id;
};
const makeUserAdminMutation = async (id: string | null) => {
if (!id) {
confirmUserToAdmin.value = false;
toast.error(t('state.admin_failure'));
return;
}
const variables = { uid: id };
const result = await userToAdmin.executeMutation(variables);
if (result.error) {
toast.error(t('state.admin_failure'));
} else {
toast.success(t('state.admin_success'));
usersList.value = usersList.value.map((user) => ({
...user,
isAdmin: user.uid === id ? true : user.isAdmin,
}));
}
confirmUserToAdmin.value = false;
userToAdminUID.value = null;
};
// Remove Admin Status from a current Admin
const adminToUser = useMutation(RemoveUserAsAdminDocument);
const confirmAdminToUser = ref(false);
const adminToUserUID = ref<string | null>(null);
const makeAdminToUser = (id: string) => {
confirmAdminToUser.value = true;
adminToUserUID.value = id;
};
const deleteUser = (id: string) => {
confirmDeletion.value = true;
deleteUserUID.value = id; deleteUserUID.value = id;
}; };
// Resets variables if user cancels the confirmation const makeAdminToUserMutation = async (id: string | null) => {
const resetConfirmUserDeletion = () => { if (!id) {
confirmUsersDeletion.value = false; confirmAdminToUser.value = false;
deleteUserUID.value = null; toast.error(t('state.remove_admin_failure'));
}; return;
const AreMultipleUsersSelectedForDeletion = computed(
() => selectedRows.value.length > 1
);
const deleteUsers = async (id: string | null) => {
const userUIDs = id ? [id] : selectedRows.value.map((user) => user.uid);
const variables = { userUIDs };
const result = await usersDeletion.executeMutation(variables);
if (result.error) {
const errorMessage =
result.error.message === DELETE_USER_FAILED_ONLY_ONE_ADMIN
? t('state.delete_user_failed_only_one_admin')
: id
? t('state.delete_user_failure')
: t('state.delete_users_failure');
toast.error(errorMessage);
} else {
const deletedUsers = result.data?.removeUsersByAdmin || [];
const deletedIDs = deletedUsers
.filter((user) => user.isDeleted)
.map((user) => user.userUID);
const isAdminError = deletedUsers.some(
(user) => user.errorMessage === ADMIN_CANNOT_BE_DELETED
);
usersList.value = usersList.value.filter(
(user) => !deletedIDs.includes(user.uid)
);
if (isAdminError) {
toast.success(
t('state.delete_some_users_success', { count: deletedIDs.length })
);
toast.error(
t('state.delete_some_users_failure', {
count: deletedUsers.length - deletedIDs.length,
})
);
toastTimeout = setTimeout(() => {
toast.error(t('state.remove_admin_for_deletion'));
}, 2000);
} else {
toast.success(
id ? t('state.delete_user_success') : t('state.delete_users_success')
);
}
selectedRows.value.splice(0, selectedRows.value.length);
} }
confirmUsersDeletion.value = false; const variables = { uid: id };
deleteUserUID.value = null; const result = await adminToUser.executeMutation(variables);
if (result.error) {
toast.error(t('state.remove_admin_failure'));
} else {
toast.success(t('state.remove_admin_success'));
usersList.value = usersList.value.map((user) => ({
...user,
isAdmin: user.uid === id ? false : user.isAdmin,
}));
}
confirmAdminToUser.value = false;
adminToUserUID.value = null;
}; };
</script> </script>

View File

@@ -6,29 +6,24 @@
</button> </button>
</div> </div>
<h3 class="text-lg font-bold text-accentContrast pt-6 pb-4"> <h3 class="text-lg font-bold text-accentContrast py-6">
{{ t('users.pending_invites') }} {{ t('users.invited_users') }}
</h3> </h3>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="relative py-2 overflow-x-auto"> <div class="py-2 overflow-x-auto">
<div v-if="fetching" class="flex justify-center"> <div v-if="fetching" class="flex justify-center">
<HoppSmartSpinner /> <HoppSmartSpinner />
</div> </div>
<div v-else-if="error"> <div v-else-if="error" class="text-xl">
{{ t('users.invite_load_list_error') }} {{ t('users.invite_load_list_error') }}
</div> </div>
<div v-else-if="pendingInvites?.length === 0">
{{ t('users.no_invite') }}
</div>
<HoppSmartTable <HoppSmartTable
v-else v-else-if="invitedUsers?.length"
:list="invitedUsers"
:headings="headings" :headings="headings"
:list="pendingInvites"
:selected-rows="selectedRows"
> >
<template #invitedOn="{ item }"> <template #invitedOn="{ item }">
<div v-if="item" class="pr-2 truncate"> <div v-if="item" class="pr-2 truncate">
@@ -42,91 +37,32 @@
</div> </div>
<span v-else> - </span> <span v-else> - </span>
</template> </template>
<template #action="{ item }">
<div v-if="item" class="my-1 mr-2">
<HoppButtonSecondary
v-if="xlAndLarger"
:icon="IconTrash"
:label="t('users.revoke_invitation')"
class="text-secondaryDark bg-red-500 hover:bg-red-600"
@click="confirmInviteDeletion(item.inviteeEmail)"
/>
<HoppButtonSecondary
v-else
v-tippy="{ theme: 'tooltip' }"
:icon="IconTrash"
:title="t('users.revoke_invitation')"
class="ml-2 !text-red-500"
@click="confirmInviteDeletion(item.inviteeEmail)"
/>
</div>
</template>
</HoppSmartTable> </HoppSmartTable>
<div <div v-else class="text-lg">{{ t('users.no_invite') }}</div>
v-if="selectedRows.length"
class="fixed m-2 bottom-0 left-40 right-0 w-min mx-auto shadow-2xl"
>
<div
class="flex justify-center items-end bg-primaryLight border border-divider rounded-md mb-5"
>
<HoppButtonSecondary
:label="t('state.selected', { count: selectedRows.length })"
class="py-4 border-divider rounded-r-none bg-emerald-800 text-secondaryDark"
/>
<HoppButtonSecondary
:icon="IconTrash"
:label="t('users.revoke_invitation')"
class="py-4 border-divider rounded-l-none hover:bg-red-500"
@click="confirmDeletion = true"
/>
</div>
</div>
</div> </div>
</div> </div>
<HoppSmartConfirmModal
:show="confirmDeletion"
:title="
selectedRows.length > 0
? t('state.confirm_delete_invites')
: t('state.confirm_delete_invite')
"
@hide-modal="confirmDeletion = false"
@resolve="deleteInvitation(inviteToBeDeleted)"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useMutation, useQuery } from '@urql/vue'; import { useQuery } from '@urql/vue';
import { breakpointsTailwind, useBreakpoints } from '@vueuse/core';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { computed, ref } from 'vue'; import { computed } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast'; import { InvitedUsersDocument } from '~/helpers/backend/graphql';
import IconTrash from '~icons/lucide/trash';
import {
InvitedUsersDocument,
InvitedUsersQuery,
RevokeUserInvitationsByAdminDocument,
} from '../../helpers/backend/graphql';
const t = useI18n(); const t = useI18n();
const toast = useToast();
const router = useRouter(); const router = useRouter();
const breakpoints = useBreakpoints(breakpointsTailwind);
const xlAndLarger = breakpoints.greater('xl');
// Get Proper Date Formats // Get Proper Date Formats
const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy'); const getCreatedDate = (date: string) => format(new Date(date), 'dd-MM-yyyy');
const getCreatedTime = (date: string) => format(new Date(date), 'hh:mm a'); const getCreatedTime = (date: string) => format(new Date(date), 'hh:mm a');
// Get Invited Users // Get Invited Users
const { fetching, error, data } = useQuery({ query: InvitedUsersDocument }); const { fetching, error, data } = useQuery({ query: InvitedUsersDocument });
const invitedUsers = computed(() => data?.value?.infra.invitedUsers);
// Table Headings // Table Headings
const headings = [ const headings = [
@@ -134,56 +70,5 @@ const headings = [
{ key: 'adminEmail', label: t('users.admin_email') }, { key: 'adminEmail', label: t('users.admin_email') },
{ key: 'inviteeEmail', label: t('users.invitee_email') }, { key: 'inviteeEmail', label: t('users.invitee_email') },
{ key: 'invitedOn', label: t('users.invited_on') }, { key: 'invitedOn', label: t('users.invited_on') },
{ key: 'action', label: 'Action' },
]; ];
// Selected Rows
const selectedRows = ref<InvitedUsersQuery['infra']['invitedUsers']>([]);
// Invited Users
const pendingInvites = computed({
get: () => data.value?.infra.invitedUsers,
set: (value) => {
if (!value) return;
data.value!.infra.invitedUsers = value;
},
});
// Delete Invite
const confirmDeletion = ref(false);
const inviteToBeDeleted = ref<string | null>(null);
const deleteInvitationMutation = useMutation(
RevokeUserInvitationsByAdminDocument
);
const confirmInviteDeletion = (inviteeEmail: string | null) => {
confirmDeletion.value = true;
inviteToBeDeleted.value = inviteeEmail;
};
const deleteInvitation = async (email: string | null) => {
const inviteeEmails = email
? [email]
: selectedRows.value.map((row) => row.inviteeEmail);
const variables = { inviteeEmails };
const result = await deleteInvitationMutation.executeMutation(variables);
if (result.error) {
email
? toast.error(t('state.delete_invite_failure'))
: toast.error(t('state.delete_invites_failure'));
} else {
pendingInvites.value = pendingInvites.value?.filter(
(user) => !inviteeEmails.includes(user.inviteeEmail)
);
selectedRows.value.splice(0, selectedRows.value.length);
email
? toast.success(t('state.delete_invite_success'))
: toast.success(t('state.delete_invites_success'));
}
confirmDeletion.value = false;
inviteToBeDeleted.value = null;
};
</script> </script>

6175
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff