feat: introducing i18n support to admin dashboard (#3051)
This commit is contained in:
committed by
GitHub
parent
b07243f131
commit
331d482b22
1
packages/hoppscotch-sh-admin/.dockerignore
Normal file
1
packages/hoppscotch-sh-admin/.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
./node_modules
|
||||||
195
packages/hoppscotch-sh-admin/languages.json
Normal file
195
packages/hoppscotch-sh-admin/languages.json
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"code": "af",
|
||||||
|
"file": "af.json",
|
||||||
|
"iso": "af-AF",
|
||||||
|
"name": "Afrikaans"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ar",
|
||||||
|
"dir": "rtl",
|
||||||
|
"file": "ar.json",
|
||||||
|
"iso": "ar-AR",
|
||||||
|
"name": "عربى"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ca",
|
||||||
|
"file": "ca.json",
|
||||||
|
"iso": "ca-CA",
|
||||||
|
"name": "Català"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "cn",
|
||||||
|
"file": "cn.json",
|
||||||
|
"iso": "zh-CN",
|
||||||
|
"name": "简体中文"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "cs",
|
||||||
|
"file": "cs.json",
|
||||||
|
"iso": "cs-CS",
|
||||||
|
"name": "Čeština"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "da",
|
||||||
|
"file": "da.json",
|
||||||
|
"iso": "da-DA",
|
||||||
|
"name": "Dansk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "de",
|
||||||
|
"file": "de.json",
|
||||||
|
"iso": "de-DE",
|
||||||
|
"name": "Deutsch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "el",
|
||||||
|
"file": "el.json",
|
||||||
|
"iso": "el-EL",
|
||||||
|
"name": "Ελληνικά"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "en",
|
||||||
|
"file": "en.json",
|
||||||
|
"iso": "en-US",
|
||||||
|
"name": "English"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "es",
|
||||||
|
"file": "es.json",
|
||||||
|
"iso": "es-ES",
|
||||||
|
"name": "Español"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "fi",
|
||||||
|
"file": "fi.json",
|
||||||
|
"iso": "fi-FI",
|
||||||
|
"name": "Suomalainen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "fr",
|
||||||
|
"file": "fr.json",
|
||||||
|
"iso": "fr-FR",
|
||||||
|
"name": "Français"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "he",
|
||||||
|
"file": "he.json",
|
||||||
|
"iso": "he-HE",
|
||||||
|
"name": "עִברִית"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "hi",
|
||||||
|
"file": "hi.json",
|
||||||
|
"iso": "hi-HI",
|
||||||
|
"name": "हिन्दी"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "hu",
|
||||||
|
"file": "hu.json",
|
||||||
|
"iso": "hu-HU",
|
||||||
|
"name": "Magyar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "id",
|
||||||
|
"file": "id.json",
|
||||||
|
"iso": "id",
|
||||||
|
"name": "Indonesian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "it",
|
||||||
|
"file": "it.json",
|
||||||
|
"iso": "it",
|
||||||
|
"name": "Italiano"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ja",
|
||||||
|
"file": "ja.json",
|
||||||
|
"iso": "ja-JA",
|
||||||
|
"name": "日本語"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ko",
|
||||||
|
"file": "ko.json",
|
||||||
|
"iso": "ko-KO",
|
||||||
|
"name": "한국어"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "nl",
|
||||||
|
"file": "nl.json",
|
||||||
|
"iso": "nl-NL",
|
||||||
|
"name": "Nederlands"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "no",
|
||||||
|
"file": "no.json",
|
||||||
|
"iso": "no-NO",
|
||||||
|
"name": "Norsk"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "pl",
|
||||||
|
"file": "pl.json",
|
||||||
|
"iso": "pl-PL",
|
||||||
|
"name": "Polskie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "pt-br",
|
||||||
|
"file": "pt-br.json",
|
||||||
|
"iso": "pt-BR",
|
||||||
|
"name": "Português Brasileiro"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "pt",
|
||||||
|
"file": "pt.json",
|
||||||
|
"iso": "pt-PT",
|
||||||
|
"name": "Português"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ro",
|
||||||
|
"file": "ro.json",
|
||||||
|
"iso": "ro-RO",
|
||||||
|
"name": "Română"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ru",
|
||||||
|
"file": "ru.json",
|
||||||
|
"iso": "ru-RU",
|
||||||
|
"name": "Pусский"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sr",
|
||||||
|
"file": "sr.json",
|
||||||
|
"iso": "sr-SR",
|
||||||
|
"name": "Српски"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "sv",
|
||||||
|
"file": "sv.json",
|
||||||
|
"iso": "sv-SV",
|
||||||
|
"name": "Svenska"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "tr",
|
||||||
|
"file": "tr.json",
|
||||||
|
"iso": "tr-TR",
|
||||||
|
"name": "Türkçe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "tw",
|
||||||
|
"file": "tw.json",
|
||||||
|
"iso": "zh-TW",
|
||||||
|
"name": "繁體中文"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "uk",
|
||||||
|
"file": "uk.json",
|
||||||
|
"iso": "uk-UK",
|
||||||
|
"name": "Українська"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "vi",
|
||||||
|
"file": "vi.json",
|
||||||
|
"iso": "vi-VI",
|
||||||
|
"name": "Tiếng Việt"
|
||||||
|
}
|
||||||
|
]
|
||||||
3
packages/hoppscotch-sh-admin/locales/af.json
Normal file
3
packages/hoppscotch-sh-admin/locales/af.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ar.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ar.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ca.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ca.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/cn.json
Normal file
3
packages/hoppscotch-sh-admin/locales/cn.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/cs.json
Normal file
3
packages/hoppscotch-sh-admin/locales/cs.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/da.json
Normal file
3
packages/hoppscotch-sh-admin/locales/da.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/de.json
Normal file
3
packages/hoppscotch-sh-admin/locales/de.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/el.json
Normal file
3
packages/hoppscotch-sh-admin/locales/el.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
132
packages/hoppscotch-sh-admin/locales/en.json
Normal file
132
packages/hoppscotch-sh-admin/locales/en.json
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"collapse_sidebar": "Collapse Sidebar",
|
||||||
|
"expand_sidebar": "Expand Sidebar",
|
||||||
|
"name": "HOPPSCOTCH",
|
||||||
|
"no_name": "No name",
|
||||||
|
"open_navigation": "Open Navigation"
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"no_metrics": "No metrics found",
|
||||||
|
"total_collections": "Total Collections",
|
||||||
|
"total_requests": "Total Requests",
|
||||||
|
"total_teams": "Total Teams",
|
||||||
|
"total_users": "Total Users"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"editor": "EDITOR",
|
||||||
|
"owner": "OWNER",
|
||||||
|
"viewer": "VIEWER"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"add_user_failure": "Failed to add user to the team!!",
|
||||||
|
"add_user_success": "User is now a member of the team!!",
|
||||||
|
"admin_failure": "Failed to make user an admin!!",
|
||||||
|
"admin_success": "User is now an admin!!",
|
||||||
|
"confirm_logout": "Confirm Logout",
|
||||||
|
"create_team_failure": "Failed to create team!!",
|
||||||
|
"create_team_success": "Team created successfully!!",
|
||||||
|
"delete_team_failure": "Team deletion failed!!",
|
||||||
|
"delete_team_success": "Team deleted successfully!!",
|
||||||
|
"delete_user_failure": "User deletion failed!!",
|
||||||
|
"delete_user_success": "User deleted successfully!!",
|
||||||
|
"email_failure": "Failed to send invitation",
|
||||||
|
"email_success": "Email invitation sent successfully",
|
||||||
|
"enter_team_email": "Please enter email of team owner!!",
|
||||||
|
"error": "Something went wrong",
|
||||||
|
"github_signin_failure": "Failed to login with Github",
|
||||||
|
"google_signin_failure": "Failed to login with Google",
|
||||||
|
"invalid_email": "Please enter a valid email address",
|
||||||
|
"logged_out": "Logged out",
|
||||||
|
"logout": "Logout",
|
||||||
|
"non_admin_login": "You are logged in. But you're not an admin",
|
||||||
|
"remove_admin_failure": "Failed to remove admin status!!",
|
||||||
|
"remove_admin_success": "Admin status removed!!",
|
||||||
|
"remove_admin_to_delete_user": "Remove admin privilege to delete the user!!",
|
||||||
|
"remove_invitee_failure": "Removal of invitee failed!!",
|
||||||
|
"remove_invitee_success": "Removal of invitee is successfull!!",
|
||||||
|
"remove_member_failure": "Member couldn't be removed!!",
|
||||||
|
"remove_member_success": "Member removed successfully!!",
|
||||||
|
"rename_team_failure": "Failed to rename team!!",
|
||||||
|
"rename_team_success": "Team renamed successfully!",
|
||||||
|
"role_update_failed": "Roles updation has failed!!",
|
||||||
|
"role_update_success": "Roles updated successfully!!",
|
||||||
|
"team_name_long": "Team name should be atleast 6 characters long!!",
|
||||||
|
"user_not_found": "User not found in the infra!!"
|
||||||
|
},
|
||||||
|
"teams": {
|
||||||
|
"add_members": "Add Members",
|
||||||
|
"admin": "Admin",
|
||||||
|
"admin_Email": "Admin Email",
|
||||||
|
"admin_id": "Admin ID",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm_team_deletion": "Confirm Deletion of the team?",
|
||||||
|
"create_team": "Create team",
|
||||||
|
"date": "Date",
|
||||||
|
"delete_team": "Delete Team",
|
||||||
|
"details": "Details",
|
||||||
|
"edit": "Edit",
|
||||||
|
"email": "Team owner email",
|
||||||
|
"email_address": "Email Address",
|
||||||
|
"error": "Something went wrong. Please try again later.",
|
||||||
|
"id": "Team ID",
|
||||||
|
"invited_email": "Invitee Email",
|
||||||
|
"invited_on": "Invited On",
|
||||||
|
"invites": "Invites",
|
||||||
|
"load_info_error": "Unable to load team info",
|
||||||
|
"load_list_error": "Unable to Load Teams List",
|
||||||
|
"members": "Number of members",
|
||||||
|
"name": "Team name",
|
||||||
|
"no_members": "No members in this team. Add members to this team to collaborate",
|
||||||
|
"no_pending_invites": "No pending invites",
|
||||||
|
"pending_invites": "Pending invites",
|
||||||
|
"remove": "Remove",
|
||||||
|
"rename": "Rename",
|
||||||
|
"save": "Save",
|
||||||
|
"send_invite": "Send Invite",
|
||||||
|
"show_more": "Show more",
|
||||||
|
"team_details": "Team details",
|
||||||
|
"team_members": "Members",
|
||||||
|
"team_members_tab": "Team members",
|
||||||
|
"teams": "Teams",
|
||||||
|
"uid": "UID",
|
||||||
|
"valid_name": "Please enter a valid team name",
|
||||||
|
"valid_owner_email": "Please enter a valid owner email"
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"admin": "Admin",
|
||||||
|
"admin_email": "Admin Email",
|
||||||
|
"admin_id": "Admin ID",
|
||||||
|
"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",
|
||||||
|
"date": "Date",
|
||||||
|
"delete": "Delete",
|
||||||
|
"email": "Email",
|
||||||
|
"email_address": "Email Address",
|
||||||
|
"id": "User ID",
|
||||||
|
"invite_user": "Invite User",
|
||||||
|
"invited_on": "Invited On",
|
||||||
|
"invitee_email": "Invitee Email",
|
||||||
|
"invited_users": "Invited Users",
|
||||||
|
"invalid_user": "Invalid User",
|
||||||
|
"load_info_error": "Unable to load user info",
|
||||||
|
"load_list_error": "Unable to Load Users List",
|
||||||
|
"make_admin": "Make admin",
|
||||||
|
"name": "Name",
|
||||||
|
"no_invite": "No invited users found",
|
||||||
|
"no_users": "No users found",
|
||||||
|
"not_found": "User not found",
|
||||||
|
"remove_admin_privilege": "Remove Admin Privilege",
|
||||||
|
"remove_admin_status": "Remove Admin Status",
|
||||||
|
"send_invite": "Send Invite",
|
||||||
|
"show_more": "Show more",
|
||||||
|
"uid": "UID",
|
||||||
|
"unnamed": "(Unnamed User)",
|
||||||
|
"user_not_found": "User not found in the infra!!",
|
||||||
|
"users": "Users",
|
||||||
|
"valid_email": "Please enter a valid email address"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/es.json
Normal file
3
packages/hoppscotch-sh-admin/locales/es.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/fi.json
Normal file
3
packages/hoppscotch-sh-admin/locales/fi.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/fr.json
Normal file
3
packages/hoppscotch-sh-admin/locales/fr.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/he.json
Normal file
3
packages/hoppscotch-sh-admin/locales/he.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/hi.json
Normal file
3
packages/hoppscotch-sh-admin/locales/hi.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/hu.json
Normal file
3
packages/hoppscotch-sh-admin/locales/hu.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/id.json
Normal file
3
packages/hoppscotch-sh-admin/locales/id.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/it.json
Normal file
3
packages/hoppscotch-sh-admin/locales/it.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ja.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ja.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ko.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ko.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/nl.json
Normal file
3
packages/hoppscotch-sh-admin/locales/nl.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/no.json
Normal file
3
packages/hoppscotch-sh-admin/locales/no.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/pl.json
Normal file
3
packages/hoppscotch-sh-admin/locales/pl.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/pt-br.json
Normal file
3
packages/hoppscotch-sh-admin/locales/pt-br.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/pt.json
Normal file
3
packages/hoppscotch-sh-admin/locales/pt.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ro.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ro.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/ru.json
Normal file
3
packages/hoppscotch-sh-admin/locales/ru.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/sr.json
Normal file
3
packages/hoppscotch-sh-admin/locales/sr.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/sv.json
Normal file
3
packages/hoppscotch-sh-admin/locales/sv.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/tr.json
Normal file
3
packages/hoppscotch-sh-admin/locales/tr.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/tw.json
Normal file
3
packages/hoppscotch-sh-admin/locales/tw.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/uk.json
Normal file
3
packages/hoppscotch-sh-admin/locales/uk.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
3
packages/hoppscotch-sh-admin/locales/vi.json
Normal file
3
packages/hoppscotch-sh-admin/locales/vi.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,12 +30,13 @@
|
|||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.16",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"rxjs": "^7.8.0",
|
"rxjs": "^7.8.0",
|
||||||
|
"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.2.6",
|
"vue": "^3.2.6",
|
||||||
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "4",
|
"vue-router": "4",
|
||||||
"tippy.js": "^6.3.7",
|
|
||||||
"vue-tippy": "6.0.0-alpha.58"
|
"vue-tippy": "6.0.0-alpha.58"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
"@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",
|
||||||
|
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
|
||||||
"@vitejs/plugin-vue": "^3.1.0",
|
"@vitejs/plugin-vue": "^3.1.0",
|
||||||
"@vue/compiler-sfc": "^3.2.6",
|
"@vue/compiler-sfc": "^3.2.6",
|
||||||
"graphql-tag": "^2.12.6",
|
"graphql-tag": "^2.12.6",
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ declare module '@vue/runtime-core' {
|
|||||||
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
|
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
|
||||||
AppToast: typeof import('./components/app/Toast.vue')['default']
|
AppToast: typeof import('./components/app/Toast.vue')['default']
|
||||||
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
|
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
|
||||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
|
||||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
|
||||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
|
||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
|
||||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
|
||||||
IconLucideUser: typeof import('~icons/lucide/user')['default']
|
|
||||||
ProfilePicture: typeof import('./components/profile/Picture.vue')['default']
|
|
||||||
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
|
||||||
TeamsDetails: typeof import('./components/teams/Details.vue')['default']
|
TeamsDetails: typeof import('./components/teams/Details.vue')['default']
|
||||||
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
|
||||||
|
|||||||
@@ -5,14 +5,18 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
title="Open navigation"
|
:title="t('app.open_navigation')"
|
||||||
:icon="IconMenu"
|
:icon="IconMenu"
|
||||||
class="transform !md:hidden mr-2"
|
class="transform !md:hidden mr-2"
|
||||||
@click="isOpen = true"
|
@click="isOpen = true"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="isExpanded ? 'Collapse sidebar' : 'Expand sidebar'"
|
:title="
|
||||||
|
isExpanded
|
||||||
|
? `${t('app.collapse_sidebar')}`
|
||||||
|
: `${t('app.expand_sidebar')}`
|
||||||
|
"
|
||||||
:icon="isExpanded ? IconSidebarClose : IconSidebarOpen"
|
:icon="isExpanded ? IconSidebarClose : IconSidebarOpen"
|
||||||
class="transform"
|
class="transform"
|
||||||
@click="expandSidebar"
|
@click="expandSidebar"
|
||||||
@@ -34,13 +38,21 @@
|
|||||||
theme: 'tooltip',
|
theme: 'tooltip',
|
||||||
}"
|
}"
|
||||||
:url="currentUser.photoURL"
|
:url="currentUser.photoURL"
|
||||||
:alt="currentUser.displayName ?? 'No Name'"
|
:alt="currentUser.displayName ?? `${t('app.no_name')}`"
|
||||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
:title="
|
||||||
|
currentUser.displayName ??
|
||||||
|
currentUser.email ??
|
||||||
|
`${t('app.no_name')}`
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
<HoppSmartPicture
|
<HoppSmartPicture
|
||||||
v-else
|
v-else
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:title="currentUser.displayName ?? currentUser.email ?? 'No Name'"
|
:title="
|
||||||
|
currentUser.displayName ??
|
||||||
|
currentUser.email ??
|
||||||
|
`${t('app.no_name')}`
|
||||||
|
"
|
||||||
:initial="currentUser.displayName ?? currentUser.email"
|
:initial="currentUser.displayName ?? currentUser.email"
|
||||||
/>
|
/>
|
||||||
<template #content="{ hide }">
|
<template #content="{ hide }">
|
||||||
@@ -70,6 +82,9 @@ import { auth } from '~/helpers/auth';
|
|||||||
import IconMenu from '~icons/lucide/menu';
|
import IconMenu from '~icons/lucide/menu';
|
||||||
import IconSidebarOpen from '~icons/lucide/sidebar-open';
|
import IconSidebarOpen from '~icons/lucide/sidebar-open';
|
||||||
import IconSidebarClose from '~icons/lucide/sidebar-close';
|
import IconSidebarClose from '~icons/lucide/sidebar-close';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const { isOpen, isExpanded } = useSidebar();
|
const { isOpen, isExpanded } = useSidebar();
|
||||||
|
|
||||||
|
|||||||
@@ -131,6 +131,9 @@ import { setLocalConfig } from '~/helpers/localpersistence';
|
|||||||
import { useStreamSubscriber } from '~/composables/stream';
|
import { useStreamSubscriber } from '~/composables/stream';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { auth } from '~/helpers/auth';
|
import { auth } from '~/helpers/auth';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const { subscribeToStream } = useStreamSubscriber();
|
const { subscribeToStream } = useStreamSubscriber();
|
||||||
|
|
||||||
@@ -158,7 +161,7 @@ onMounted(() => {
|
|||||||
subscribeToStream(currentUser$, (user) => {
|
subscribeToStream(currentUser$, (user) => {
|
||||||
if (user && !user.isAdmin) {
|
if (user && !user.isAdmin) {
|
||||||
nonAdminUser.value = true;
|
nonAdminUser.value = true;
|
||||||
toast.error(`You are logged in. But you're not an admin`);
|
toast.error(`${t('state.non_admin_login')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -174,7 +177,7 @@ async function signInWithGoogle() {
|
|||||||
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
||||||
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
||||||
*/
|
*/
|
||||||
toast.error(`Failed to sign in with Google`);
|
toast.error(`${t('state.google_signin_failure')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
signingInWithGoogle.value = false;
|
signingInWithGoogle.value = false;
|
||||||
@@ -190,7 +193,7 @@ async function signInWithGithub() {
|
|||||||
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
A auth/account-exists-with-different-credential Firebase error wont happen between Google and any other providers
|
||||||
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
Seems Google account overwrites accounts of other providers https://github.com/firebase/firebase-android-sdk/issues/25
|
||||||
*/
|
*/
|
||||||
toast.error(`Failed to sign in with GitHub`);
|
toast.error(`${t('state.github_signin_failure')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
signingInWithGitHub.value = false;
|
signingInWithGitHub.value = false;
|
||||||
@@ -211,7 +214,7 @@ async function signInWithMicrosoft() {
|
|||||||
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
|
@firebase/auth: Auth (9.6.11): INTERNAL ASSERTION FAILED: Pending promise was never set
|
||||||
They may be related to https://github.com/firebase/firebaseui-web/issues/947
|
They may be related to https://github.com/firebase/firebaseui-web/issues/947
|
||||||
*/
|
*/
|
||||||
toast.error(`Something went wrong`);
|
toast.error(`${t('state.error')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
signingInWithMicrosoft.value = false;
|
signingInWithMicrosoft.value = false;
|
||||||
@@ -239,10 +242,10 @@ const logout = async () => {
|
|||||||
try {
|
try {
|
||||||
await auth.signOutUser();
|
await auth.signOutUser();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
toast.success(`Logged out`);
|
toast.success(`${t('state.logged_out')}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error(`Something went wrong`);
|
toast.error(`${t('state.error')}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
<div class="flex" @click="openLogoutModal()">
|
<div class="flex" @click="openLogoutModal()">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
:icon="IconLogOut"
|
:icon="IconLogOut"
|
||||||
:label="'Logout'"
|
:label="t('state.logout')"
|
||||||
:outline="outline"
|
:outline="outline"
|
||||||
:shortcut="shortcut"
|
:shortcut="shortcut"
|
||||||
@click="openLogoutModal()"
|
@click="openLogoutModal()"
|
||||||
/>
|
/>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmLogout"
|
:show="confirmLogout"
|
||||||
:title="`Confirm Logout`"
|
:title="t('state.confirm_logout')"
|
||||||
@hide-modal="confirmLogout = false"
|
@hide-modal="confirmLogout = false"
|
||||||
@resolve="logout"
|
@resolve="logout"
|
||||||
/>
|
/>
|
||||||
@@ -22,6 +22,9 @@ import IconLogOut from '~icons/lucide/log-out';
|
|||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { auth } from '~/helpers/auth';
|
import { auth } from '~/helpers/auth';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -48,10 +51,10 @@ const logout = async () => {
|
|||||||
try {
|
try {
|
||||||
await auth.signOutUser();
|
await auth.signOutUser();
|
||||||
router.push(`/`);
|
router.push(`/`);
|
||||||
toast.success(`Logged out`);
|
toast.success(`${t('state.logged_out')}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error(`Something went wrong`);
|
toast.error(`${t('state.error')}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
<HoppSmartLink class="flex items-center space-x-4" to="/dashboard">
|
<HoppSmartLink class="flex items-center space-x-4" to="/dashboard">
|
||||||
<img src="/cover.jpg" alt="hoppscotch-logo" class="h-7" />
|
<img src="/cover.jpg" alt="hoppscotch-logo" class="h-7" />
|
||||||
|
|
||||||
<span v-if="isExpanded" class="font-semibold text-accentContrast"
|
<span
|
||||||
>HOPPSCOTCH</span
|
v-if="isExpanded"
|
||||||
|
class="font-semibold text-accentContrast"
|
||||||
|
>{{ t('app.name') }}</span
|
||||||
>
|
>
|
||||||
</HoppSmartLink>
|
</HoppSmartLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -59,28 +61,31 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { HoppSmartLink } from '@hoppscotch/ui';
|
import { HoppSmartLink } from '@hoppscotch/ui';
|
||||||
import { useSidebar } from '../../composables/useSidebar';
|
import { useSidebar } from '~/composables/useSidebar';
|
||||||
import IconDashboard from '~icons/lucide/layout-dashboard';
|
import IconDashboard from '~icons/lucide/layout-dashboard';
|
||||||
import IconUser from '~icons/lucide/user';
|
import IconUser from '~icons/lucide/user';
|
||||||
import IconUsers from '~icons/lucide/users';
|
import IconUsers from '~icons/lucide/users';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const { isOpen, isExpanded } = useSidebar();
|
const { isOpen, isExpanded } = useSidebar();
|
||||||
|
|
||||||
const primaryNavigations = [
|
const primaryNavigations = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: t('metrics.dashboard'),
|
||||||
icon: IconDashboard,
|
icon: IconDashboard,
|
||||||
to: '/dashboard',
|
to: '/dashboard',
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Users',
|
label: t('users.users'),
|
||||||
icon: IconUser,
|
icon: IconUser,
|
||||||
to: '/users',
|
to: '/users',
|
||||||
exact: false,
|
exact: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Teams',
|
label: t('teams.teams'),
|
||||||
icon: IconUsers,
|
icon: IconUsers,
|
||||||
to: '/teams',
|
to: '/teams',
|
||||||
exact: false,
|
exact: false,
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<HoppSmartModal
|
<HoppSmartModal
|
||||||
v-if="show"
|
v-if="show"
|
||||||
dialog
|
dialog
|
||||||
title="Create team"
|
:title="t('teams.create_team')"
|
||||||
@close="$emit('hide-modal')"
|
@close="$emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
<div class="flex flex-col space-y-4 relative">
|
<div class="flex flex-col space-y-4 relative">
|
||||||
<div class="flex flex-col relaive">
|
<div class="flex flex-col relaive">
|
||||||
<label for="teamName" class="py-2"> Team owner email </label>
|
<label for="teamName" class="py-2"> {{ t('teams.email') }} </label>
|
||||||
<HoppSmartAutoComplete
|
<HoppSmartAutoComplete
|
||||||
styles="w-full p-2 bg-transparent border border-divider rounded-md "
|
styles="w-full p-2 bg-transparent border border-divider rounded-md "
|
||||||
class="flex-1 !flex"
|
class="flex-1 !flex"
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label for="teamName" class="py-2">Team name</label>
|
<label for="teamName" class="py-2">{{ t('teams.name') }} </label>
|
||||||
<input
|
<input
|
||||||
id="teamName"
|
id="teamName"
|
||||||
v-model="teamName"
|
v-model="teamName"
|
||||||
@@ -35,11 +35,16 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
label="Create team"
|
:label="t('teams.create_team')"
|
||||||
:loading="loadingState"
|
:loading="loadingState"
|
||||||
@click="createTeam"
|
@click="createTeam"
|
||||||
/>
|
/>
|
||||||
<HoppButtonSecondary label="Cancel" outline filled @click="hideModal" />
|
<HoppButtonSecondary
|
||||||
|
:label="t('teams.cancel')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="hideModal"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</HoppSmartModal>
|
</HoppSmartModal>
|
||||||
@@ -48,6 +53,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -75,11 +83,11 @@ const getOwnerEmail = (email: string) => (ownerEmail.value = email);
|
|||||||
|
|
||||||
const createTeam = () => {
|
const createTeam = () => {
|
||||||
if (teamName.value.trim() === '') {
|
if (teamName.value.trim() === '') {
|
||||||
toast.error('Please enter a valid team name');
|
toast.error(`${t('teams.valid_name')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ownerEmail.value.trim() === '') {
|
if (ownerEmail.value.trim() === '') {
|
||||||
toast.error('Please enter a valid owner email');
|
toast.error(`${t('teams.valid_owner_email')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit('create-team', teamName.value, ownerEmail.value);
|
emit('create-team', teamName.value, ownerEmail.value);
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col space-y-8">
|
<div class="flex flex-col space-y-8">
|
||||||
<div v-if="team.id" class="flex flex-col space-y-3">
|
<div v-if="team.id" class="flex flex-col space-y-3">
|
||||||
<label class="text-accentContrast" for="username">Team ID</label>
|
<label class="text-accentContrast" for="username"
|
||||||
|
>{{ t('teams.id') }}
|
||||||
|
</label>
|
||||||
<div class="w-full p-3 bg-divider rounded-md">
|
<div class="w-full p-3 bg-divider rounded-md">
|
||||||
{{ team.id }}
|
{{ team.id }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="teamName" class="flex flex-col space-y-3">
|
<div v-if="teamName" class="flex flex-col space-y-3">
|
||||||
<label class="text-accentContrast" for="teamname">Team Name </label>
|
<label class="text-accentContrast" for="teamname"
|
||||||
|
>{{ t('teams.name') }}
|
||||||
|
</label>
|
||||||
<div
|
<div
|
||||||
class="flex bg-divider rounded-md items-stretch flex-1 border border-divider"
|
class="flex bg-divider rounded-md items-stretch flex-1 border border-divider"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -29,7 +33,9 @@
|
|||||||
class="!rounded-l-none"
|
class="!rounded-l-none"
|
||||||
filled
|
filled
|
||||||
:icon="showRenameInput ? IconSave : IconEdit"
|
:icon="showRenameInput ? IconSave : IconEdit"
|
||||||
:label="showRenameInput ? 'Rename' : 'Edit'"
|
:label="
|
||||||
|
showRenameInput ? `${t('teams.rename')}` : `${t('teams.edit')}`
|
||||||
|
"
|
||||||
@click="handleNameEdit()"
|
@click="handleNameEdit()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,8 +43,8 @@
|
|||||||
|
|
||||||
<div v-if="team.teamMembers.length" class="flex flex-col space-y-3">
|
<div v-if="team.teamMembers.length" class="flex flex-col space-y-3">
|
||||||
<label class="text-accentContrast" for="username"
|
<label class="text-accentContrast" for="username"
|
||||||
>Number of Members</label
|
>{{ t('teams.members') }}
|
||||||
>
|
</label>
|
||||||
<div class="w-full p-3 bg-divider rounded-md">
|
<div class="w-full p-3 bg-divider rounded-md">
|
||||||
{{ team.teamMembers.length }}
|
{{ team.teamMembers.length }}
|
||||||
</div>
|
</div>
|
||||||
@@ -49,7 +55,7 @@
|
|||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
class="!bg-red-600 !hover:opacity-80"
|
class="!bg-red-600 !hover:opacity-80"
|
||||||
filled
|
filled
|
||||||
label="Delete Team"
|
:label="t('teams.delete_team')"
|
||||||
@click="team && $emit('delete-team', team.id)"
|
@click="team && $emit('delete-team', team.id)"
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
/>
|
/>
|
||||||
@@ -58,12 +64,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { TeamInfoQuery } from '~/helpers/backend/graphql';
|
import { TeamInfoQuery } from '~/helpers/backend/graphql';
|
||||||
import IconEdit from '~icons/lucide/edit';
|
import IconEdit from '~icons/lucide/edit';
|
||||||
import IconSave from '~icons/lucide/save';
|
import IconSave from '~icons/lucide/save';
|
||||||
import IconTrash from '~icons/lucide/trash-2';
|
import IconTrash from '~icons/lucide/trash-2';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -91,7 +100,7 @@ const handleNameEdit = () => {
|
|||||||
|
|
||||||
const renameTeam = () => {
|
const renameTeam = () => {
|
||||||
if (newTeamName.value.trim() === '') {
|
if (newTeamName.value.trim() === '') {
|
||||||
toast.error('Team name cannot be empty');
|
toast.error(`${t('teams.empty_name')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit('rename-team', newTeamName.value);
|
emit('rename-team', newTeamName.value);
|
||||||
|
|||||||
@@ -115,11 +115,6 @@
|
|||||||
v-if="newMembersList.length === 0"
|
v-if="newMembersList.length === 0"
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
>
|
>
|
||||||
<img
|
|
||||||
:src="`/images/states/dark/add_group.svg`"
|
|
||||||
loading="lazy"
|
|
||||||
class="inline-flex flex-col object-contain object-center w-16 h-16 mb-4"
|
|
||||||
/>
|
|
||||||
<span class="pb-4 text-center"> No invites </span>
|
<span class="pb-4 text-center"> No invites </span>
|
||||||
<HoppButtonSecondary label="Add new" filled @click="addNewMember" />
|
<HoppButtonSecondary label="Add new" filled @click="addNewMember" />
|
||||||
</div>
|
</div>
|
||||||
@@ -131,7 +126,9 @@
|
|||||||
<span
|
<span
|
||||||
class="flex items-center justify-center px-2 py-1 mb-4 font-semibold border rounded-full bg-primaryDark border-divider"
|
class="flex items-center justify-center px-2 py-1 mb-4 font-semibold border rounded-full bg-primaryDark border-divider"
|
||||||
>
|
>
|
||||||
<icon-lucide-help-circle class="mr-2 text-secondaryLight svg-icons" />
|
<icon-lucide-help-circle
|
||||||
|
class="mr-2 text-secondaryLight svg-icons"
|
||||||
|
/>
|
||||||
Roles
|
Roles
|
||||||
</span>
|
</span>
|
||||||
<p>
|
<p>
|
||||||
@@ -209,6 +206,9 @@ import IconCircleDot from '~icons/lucide/circle-dot';
|
|||||||
import IconCircle from '~icons/lucide/circle';
|
import IconCircle from '~icons/lucide/circle';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { usePagedQuery } from '~/composables/usePagedQuery';
|
import { usePagedQuery } from '~/composables/usePagedQuery';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
// Get Users List
|
// Get Users List
|
||||||
const { data } = useQuery({ query: MetricsDocument });
|
const { data } = useQuery({ query: MetricsDocument });
|
||||||
@@ -286,7 +286,7 @@ const addUserasTeamMember = async () => {
|
|||||||
|
|
||||||
if (O.isNone(validationResult)) {
|
if (O.isNone(validationResult)) {
|
||||||
// Error handling for no validation
|
// Error handling for no validation
|
||||||
toast.error('Invalid User!!');
|
toast.error(`${t('users.invalid_user')}`);
|
||||||
addingUserToTeam.value = false;
|
addingUserToTeam.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -318,12 +318,12 @@ const addUserToTeam = async (
|
|||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
if (result.error.toString() == '[GraphQL] user/not_found') {
|
if (result.error.toString() == '[GraphQL] user/not_found') {
|
||||||
toast.error('User not found in the infra!!');
|
toast.error(`${t('state.user_not_found')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Failed to add user to the team!!');
|
toast.error(`${t('state.add_user_failure')}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.success('User is now a member of the team!!');
|
toast.success(`${t('state.add_user_success')}`);
|
||||||
emit('member');
|
emit('member');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:icon="IconUserPlus"
|
:icon="IconUserPlus"
|
||||||
label="Add Members"
|
:label="t('teams.add_members')"
|
||||||
filled
|
filled
|
||||||
@click="showInvite = !showInvite"
|
@click="showInvite = !showInvite"
|
||||||
/>
|
/>
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
>
|
>
|
||||||
<span class="pb-4 text-center">
|
<span class="pb-4 text-center">
|
||||||
No members in this team. Add members to this team to collaborate
|
{{ t('teams.no_members') }}
|
||||||
</span>
|
</span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
:icon="IconUserPlus"
|
:icon="IconUserPlus"
|
||||||
label="Add Members"
|
:label="t('teams.add_members')"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
showInvite = !showInvite;
|
showInvite = !showInvite;
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
id="member"
|
id="member"
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
title="Remove"
|
:title="t('teams.remove')"
|
||||||
:icon="IconUserMinus"
|
:icon="IconUserMinus"
|
||||||
color="red"
|
color="red"
|
||||||
:loading="isLoadingIndex === index"
|
:loading="isLoadingIndex === index"
|
||||||
@@ -134,12 +134,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!fetching && !team" class="flex flex-col items-center">
|
<div v-if="!fetching && !team" class="flex flex-col items-center">
|
||||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
Something went wrong. Please try again later.
|
{{ t('teams.error') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<HoppButtonPrimary label="Save" outline @click="saveUpdatedTeam" />
|
<HoppButtonPrimary
|
||||||
|
:label="t('teams.save')"
|
||||||
|
outline
|
||||||
|
@click="saveUpdatedTeam"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<TeamsInvite
|
<TeamsInvite
|
||||||
:show="showInvite"
|
:show="showInvite"
|
||||||
@@ -163,7 +167,7 @@ import IconChevronDown from '~icons/lucide/chevron-down';
|
|||||||
import { useClientHandle, useMutation } from '@urql/vue';
|
import { useClientHandle, useMutation } from '@urql/vue';
|
||||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useToast } from '../../composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import {
|
import {
|
||||||
ChangeUserRoleInTeamByAdminDocument,
|
ChangeUserRoleInTeamByAdminDocument,
|
||||||
TeamInfoDocument,
|
TeamInfoDocument,
|
||||||
@@ -172,6 +176,9 @@ import {
|
|||||||
TeamInfoQuery,
|
TeamInfoQuery,
|
||||||
} from '../../helpers/backend/graphql';
|
} from '../../helpers/backend/graphql';
|
||||||
import { HoppButtonPrimary, HoppButtonSecondary } from '@hoppscotch/ui';
|
import { HoppButtonPrimary, HoppButtonSecondary } from '@hoppscotch/ui';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -195,7 +202,7 @@ const getTeamInfo = async () => {
|
|||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
return toast.error('Unable to Load Team Info..');
|
return toast.error(`${t('teams.load_info_error')}`);
|
||||||
}
|
}
|
||||||
if (result.data?.admin.teamInfo) {
|
if (result.data?.admin.teamInfo) {
|
||||||
team.value = result.data.admin.teamInfo;
|
team.value = result.data.admin.teamInfo;
|
||||||
@@ -301,10 +308,10 @@ const saveUpdatedTeam = async () => {
|
|||||||
update.role
|
update.role
|
||||||
);
|
);
|
||||||
if (updateMemberRoleResult.error) {
|
if (updateMemberRoleResult.error) {
|
||||||
toast.error('Role updation has failed!!');
|
toast.error(`${t('state.role_update_failed')}`);
|
||||||
roleUpdates.value = [];
|
roleUpdates.value = [];
|
||||||
} else {
|
} else {
|
||||||
toast.success('Roles updated successfully!!');
|
toast.success(`${t('state.role_update_success')}`);
|
||||||
roleUpdates.value = [];
|
roleUpdates.value = [];
|
||||||
}
|
}
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
@@ -334,12 +341,12 @@ const removeExistingTeamMember = async (userID: string, index: number) => {
|
|||||||
team.value.id
|
team.value.id
|
||||||
)();
|
)();
|
||||||
if (removeTeamMemberResult.error) {
|
if (removeTeamMemberResult.error) {
|
||||||
toast.error(`Member couldn't be removed!!`);
|
toast.error(`${t('state.remove_member_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
team.value.teamMembers = team.value.teamMembers?.filter(
|
team.value.teamMembers = team.value.teamMembers?.filter(
|
||||||
(member: any) => member.user.uid !== userID
|
(member: any) => member.user.uid !== userID
|
||||||
);
|
);
|
||||||
toast.success('Member removed successfully!!');
|
toast.success(`${t('state.remove_member_success')}`);
|
||||||
}
|
}
|
||||||
isLoadingIndex.value = null;
|
isLoadingIndex.value = null;
|
||||||
emit('update-team');
|
emit('update-team');
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
title="Remove"
|
:title="t('teams.remove')"
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
color="red"
|
color="red"
|
||||||
:loading="isLoadingIndex === index"
|
:loading="isLoadingIndex === index"
|
||||||
@@ -41,11 +41,11 @@
|
|||||||
v-if="team && pendingInvites?.length === 0"
|
v-if="team && pendingInvites?.length === 0"
|
||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
>
|
>
|
||||||
<span class="text-center"> No pending invites </span>
|
<span class="text-center">{{ t('teams.no_pending_invites') }} </span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!fetching && error" class="flex flex-col items-center p-4">
|
<div v-if="!fetching && error" class="flex flex-col items-center p-4">
|
||||||
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
<icon-lucide-help-circle class="mb-4 svg-icons" />
|
||||||
Something went wrong. Please try again later.
|
{{ t('teams.error') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,6 +62,9 @@ import {
|
|||||||
} from '~/helpers/backend/graphql';
|
} from '~/helpers/backend/graphql';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -83,7 +86,7 @@ const getTeamInfo = async () => {
|
|||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
error.value = true;
|
error.value = true;
|
||||||
return toast.error('Unable to load team details..');
|
return toast.error(`${t('teams.load_info_error')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.data?.admin.teamInfo) {
|
if (result.data?.admin.teamInfo) {
|
||||||
@@ -106,7 +109,7 @@ const removeInvitee = async (id: string, index: number) => {
|
|||||||
isLoadingIndex.value = index;
|
isLoadingIndex.value = index;
|
||||||
const result = await revokeTeamInvitation(id);
|
const result = await revokeTeamInvitation(id);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Removal of invitee failed!!');
|
toast.error(`${t('state.remove_invitee_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
if (pendingInvites.value) {
|
if (pendingInvites.value) {
|
||||||
pendingInvites.value = pendingInvites.value.filter(
|
pendingInvites.value = pendingInvites.value.filter(
|
||||||
@@ -114,7 +117,7 @@ const removeInvitee = async (id: string, index: number) => {
|
|||||||
return invite.id !== id;
|
return invite.id !== id;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
toast.success('Removal of invitee is successfull!!');
|
toast.success(`${t('state.remove_invitee_success')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isLoadingIndex.value = null;
|
isLoadingIndex.value = null;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<HoppSmartModal
|
<HoppSmartModal
|
||||||
v-if="show"
|
v-if="show"
|
||||||
dialog
|
dialog
|
||||||
title="Invite User"
|
:title="t('users.invite_user')"
|
||||||
@close="$emit('hide-modal')"
|
@close="$emit('hide-modal')"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
@@ -17,13 +17,13 @@
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@keyup.enter="sendInvite"
|
@keyup.enter="sendInvite"
|
||||||
/>
|
/>
|
||||||
<label for="inviteUserEmail">Email Address</label>
|
<label for="inviteUserEmail">{{ t('users.email_address') }}</label>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="flex space-x-2">
|
<span class="flex space-x-2">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
label="Send Invite"
|
:label="t('users.send_invite')"
|
||||||
:loading="loadingState"
|
:loading="loadingState"
|
||||||
@click="sendInvite"
|
@click="sendInvite"
|
||||||
/>
|
/>
|
||||||
@@ -36,6 +36,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useToast } from '~/composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -59,7 +62,7 @@ const email = ref('');
|
|||||||
|
|
||||||
const sendInvite = () => {
|
const sendInvite = () => {
|
||||||
if (email.value.trim() === '') {
|
if (email.value.trim() === '') {
|
||||||
toast.error('Please enter a valid email address');
|
toast.error(`${t('users.valid_email')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit('send-invite', email.value);
|
emit('send-invite', email.value);
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<table class="w-full">
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-secondary border-b border-dividerDark text-sm text-left">
|
<tr class="text-secondary border-b border-dividerDark text-sm text-left">
|
||||||
<th class="px-3 pb-3">User ID</th>
|
<th class="px-3 pb-3">{{ t('users.id') }}</th>
|
||||||
<th class="px-3 pb-3">Name</th>
|
<th class="px-3 pb-3">{{ t('users.name') }}</th>
|
||||||
<th class="px-3 pb-3">Email</th>
|
<th class="px-3 pb-3">{{ t('users.email') }}</th>
|
||||||
<th class="px-3 pb-3">Date</th>
|
<th class="px-3 pb-3">{{ t('users.date') }}</th>
|
||||||
<th class="px-3 pb-3"></th>
|
<th class="px-3 pb-3"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -36,16 +36,16 @@
|
|||||||
v-if="user.isAdmin"
|
v-if="user.isAdmin"
|
||||||
class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
||||||
>
|
>
|
||||||
Admin
|
{{ t('users.admin') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex items-center space-x-3">
|
<div v-else class="flex items-center space-x-3">
|
||||||
<span> (Unnamed user) </span>
|
<span> {{ t('users.unnamed') }} </span>
|
||||||
<span
|
<span
|
||||||
v-if="user.isAdmin"
|
v-if="user.isAdmin"
|
||||||
class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
class="text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
||||||
>
|
>
|
||||||
Admin
|
{{ t('users.admin') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -138,6 +138,9 @@ import IconUserCheck from '~icons/lucide/user-check';
|
|||||||
import IconMoreHorizontal from '~icons/lucide/more-horizontal';
|
import IconMoreHorizontal from '~icons/lucide/more-horizontal';
|
||||||
import { UsersListQuery } from '~/helpers/backend/graphql';
|
import { UsersListQuery } from '~/helpers/backend/graphql';
|
||||||
import { TippyComponent } from 'vue-tippy';
|
import { TippyComponent } from 'vue-tippy';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
usersList: UsersListQuery['admin']['allUsers'];
|
usersList: UsersListQuery['admin']['allUsers'];
|
||||||
|
|||||||
6
packages/hoppscotch-sh-admin/src/composables/i18n.ts
Normal file
6
packages/hoppscotch-sh-admin/src/composables/i18n.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { flow } from "fp-ts/function"
|
||||||
|
import { useI18n as _useI18n } from "vue-i18n"
|
||||||
|
|
||||||
|
export const useI18n = flow(_useI18n, (x) => x.t)
|
||||||
|
|
||||||
|
export const useFullI18n = _useI18n
|
||||||
3
packages/hoppscotch-sh-admin/src/helpers/error.ts
Normal file
3
packages/hoppscotch-sh-admin/src/helpers/error.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const throwError = (message: string): never => {
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
154
packages/hoppscotch-sh-admin/src/modules/i18n.ts
Normal file
154
packages/hoppscotch-sh-admin/src/modules/i18n.ts
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import * as R from 'fp-ts/Record';
|
||||||
|
import * as A from 'fp-ts/Array';
|
||||||
|
import * as O from 'fp-ts/Option';
|
||||||
|
import { pipe } from 'fp-ts/function';
|
||||||
|
import { createI18n, I18n, I18nOptions } from 'vue-i18n';
|
||||||
|
import { HoppModule } from '.';
|
||||||
|
|
||||||
|
import languages from '../../languages.json';
|
||||||
|
|
||||||
|
import { throwError } from '../helpers/error';
|
||||||
|
import { getLocalConfig, setLocalConfig } from '../helpers/localpersistence';
|
||||||
|
|
||||||
|
/*
|
||||||
|
In context of this file, we have 2 main kinds of things.
|
||||||
|
1. Locale -> A locale is termed as the i18n entries present in the /locales folder
|
||||||
|
2. Language -> A language is an entry in the /languages.json folder
|
||||||
|
|
||||||
|
Each language entry should correspond to a locale entry.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* As we migrate out of Nuxt I18n into our own system for i18n management,
|
||||||
|
* Some stuff has changed regarding how it works.
|
||||||
|
*
|
||||||
|
* The previous system works by using paths to represent locales to load.
|
||||||
|
* Basically, /es/realtime will load the /realtime page but with 'es' language
|
||||||
|
*
|
||||||
|
* In the new system instead of relying on the lang code, we store the language
|
||||||
|
* in the application local config store (localStorage). The URLs don't have
|
||||||
|
* a locale path effect
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Syncing into settings ?
|
||||||
|
|
||||||
|
const LOCALES = import.meta.glob('../../locales/*.json');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log(LOCALES);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
type LanguagesDef = {
|
||||||
|
code: string;
|
||||||
|
file: string;
|
||||||
|
iso: string;
|
||||||
|
name: string;
|
||||||
|
dir?: 'ltr' | 'rtl'; // Text Orientation (defaults to 'ltr')
|
||||||
|
};
|
||||||
|
|
||||||
|
const FALLBACK_LANG_CODE = 'en';
|
||||||
|
|
||||||
|
// TypeScript cannot understand dir is restricted to "ltr" or "rtl" yet, hence assertion
|
||||||
|
export const APP_LANGUAGES: LanguagesDef[] = languages as LanguagesDef[];
|
||||||
|
|
||||||
|
export const APP_LANG_CODES = languages.map(({ code }) => code);
|
||||||
|
|
||||||
|
export const FALLBACK_LANG = pipe(
|
||||||
|
APP_LANGUAGES,
|
||||||
|
A.findFirst((x) => x.code === FALLBACK_LANG_CODE),
|
||||||
|
O.getOrElseW(() =>
|
||||||
|
throwError(`Could not find the fallback language '${FALLBACK_LANG_CODE}'`)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// A reference to the i18n instance
|
||||||
|
let i18nInstance: I18n<any, any, any> | null = null;
|
||||||
|
|
||||||
|
const resolveCurrentLocale = () =>
|
||||||
|
pipe(
|
||||||
|
// Resolve from locale and make sure it is in languages
|
||||||
|
getLocalConfig('locale'),
|
||||||
|
O.fromNullable,
|
||||||
|
O.filter((locale) =>
|
||||||
|
pipe(
|
||||||
|
APP_LANGUAGES,
|
||||||
|
A.some(({ code }) => code === locale)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Else load from navigator.language
|
||||||
|
O.alt(() =>
|
||||||
|
pipe(
|
||||||
|
APP_LANGUAGES,
|
||||||
|
A.findFirst(({ code }) => navigator.language.startsWith(code)), // en-US should also match to en
|
||||||
|
O.map(({ code }) => code)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
// Else load fallback
|
||||||
|
O.getOrElse(() => FALLBACK_LANG_CODE)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the application language. This function returns a promise as
|
||||||
|
* the locale files are lazy loaded on demand
|
||||||
|
* @param locale The locale code of the language to load
|
||||||
|
*/
|
||||||
|
export const changeAppLanguage = async (locale: string) => {
|
||||||
|
const localeData = (
|
||||||
|
(await pipe(
|
||||||
|
LOCALES,
|
||||||
|
R.lookup(`../../locales/${locale}.json`),
|
||||||
|
O.getOrElseW(() =>
|
||||||
|
throwError(
|
||||||
|
`Tried to change app language to non-existent locale '${locale}'`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)()) as any
|
||||||
|
).default;
|
||||||
|
|
||||||
|
if (!i18nInstance) {
|
||||||
|
throw new Error('Tried to change language without active i18n instance');
|
||||||
|
}
|
||||||
|
|
||||||
|
i18nInstance.global.setLocaleMessage(locale, localeData);
|
||||||
|
|
||||||
|
// TODO: Look into the type issues here
|
||||||
|
i18nInstance.global.locale.value = locale;
|
||||||
|
|
||||||
|
setLocalConfig('locale', locale);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default <HoppModule>{
|
||||||
|
onVueAppInit(app) {
|
||||||
|
const i18n = createI18n(<I18nOptions>{
|
||||||
|
locale: 'en', // TODO: i18n system!
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
legacy: false,
|
||||||
|
allowComposition: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(i18n);
|
||||||
|
|
||||||
|
i18nInstance = i18n;
|
||||||
|
|
||||||
|
// TODO: Global loading state to hide the resolved lang loading
|
||||||
|
const currentLocale = resolveCurrentLocale();
|
||||||
|
changeAppLanguage(currentLocale);
|
||||||
|
|
||||||
|
setLocalConfig('locale', currentLocale);
|
||||||
|
},
|
||||||
|
onBeforeRouteChange(to, _, router) {
|
||||||
|
// Convert old locale path format to new format
|
||||||
|
const oldLocalePathLangCode = APP_LANG_CODES.find((langCode) =>
|
||||||
|
to.path.startsWith(`/${langCode}/`)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change language to the correct lang code
|
||||||
|
if (oldLocalePathLangCode) {
|
||||||
|
changeAppLanguage(oldLocalePathLangCode);
|
||||||
|
|
||||||
|
router.replace(to.path.substring(`/${oldLocalePathLangCode}`.length));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,38 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-lg font-bold text-secondaryDark">Dashboard</h1>
|
<h1 class="text-lg font-bold text-secondaryDark">
|
||||||
|
{{ t('metrics.dashboard') }}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div v-if="fetching" class="flex justify-center py-6">
|
<div v-if="fetching" class="flex justify-center py-6">
|
||||||
<HoppSmartSpinner />
|
<HoppSmartSpinner />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error || !metrics">
|
<div v-else-if="error || !metrics">
|
||||||
<p class="text-xl">No Metrics Found..</p>
|
<p class="text-xl">{{ t('metrics.no_metrics') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="py-10 grid lg:grid-cols-2 gap-6">
|
<div class="py-10 grid lg:grid-cols-2 gap-6">
|
||||||
<DashboardMetricsCard
|
<DashboardMetricsCard
|
||||||
:count="metrics.usersCount"
|
:count="metrics.usersCount"
|
||||||
label="Total Users"
|
:label="t('metrics.total_users')"
|
||||||
:icon="UserIcon"
|
:icon="UserIcon"
|
||||||
color="text-green-400"
|
color="text-green-400"
|
||||||
/>
|
/>
|
||||||
<DashboardMetricsCard
|
<DashboardMetricsCard
|
||||||
:count="metrics.teamsCount"
|
:count="metrics.teamsCount"
|
||||||
label="Total Teams"
|
:label="t('metrics.total_teams')"
|
||||||
:icon="UsersIcon"
|
:icon="UsersIcon"
|
||||||
color="text-pink-400"
|
color="text-pink-400"
|
||||||
/>
|
/>
|
||||||
<DashboardMetricsCard
|
<DashboardMetricsCard
|
||||||
:count="metrics.teamRequestsCount"
|
:count="metrics.teamRequestsCount"
|
||||||
label="Total Requests"
|
:label="t('metrics.total_requests')"
|
||||||
:icon="LineChartIcon"
|
:icon="LineChartIcon"
|
||||||
color="text-cyan-400"
|
color="text-cyan-400"
|
||||||
/>
|
/>
|
||||||
<DashboardMetricsCard
|
<DashboardMetricsCard
|
||||||
:count="metrics.teamCollectionsCount"
|
:count="metrics.teamCollectionsCount"
|
||||||
label="Total Collections"
|
:label="t('metrics.total_collections')"
|
||||||
:icon="FolderTreeIcon"
|
:icon="FolderTreeIcon"
|
||||||
color="text-orange-400"
|
color="text-orange-400"
|
||||||
/>
|
/>
|
||||||
@@ -49,6 +51,9 @@ import UserIcon from '~icons/lucide/user';
|
|||||||
import UsersIcon from '~icons/lucide/users';
|
import UsersIcon from '~icons/lucide/users';
|
||||||
import LineChartIcon from '~icons/lucide/line-chart';
|
import LineChartIcon from '~icons/lucide/line-chart';
|
||||||
import FolderTreeIcon from '~icons/lucide/folder-tree';
|
import FolderTreeIcon from '~icons/lucide/folder-tree';
|
||||||
|
import { useI18n } from '../composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
// Get Metrics Data
|
// Get Metrics Data
|
||||||
const { fetching, error, data } = useQuery({ query: MetricsDocument });
|
const { fetching, error, data } = useQuery({ query: MetricsDocument });
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<div class="py-8">
|
<div class="py-8">
|
||||||
<HoppSmartTabs v-model="selectedOptionTab" render-inactive-tabs>
|
<HoppSmartTabs v-model="selectedOptionTab" render-inactive-tabs>
|
||||||
<HoppSmartTab :id="'details'" label="Details">
|
<HoppSmartTab :id="'details'" :label="t('teams.details')">
|
||||||
<TeamsDetails
|
<TeamsDetails
|
||||||
:team="team"
|
:team="team"
|
||||||
:teamName="teamName"
|
:teamName="teamName"
|
||||||
@@ -35,17 +35,17 @@
|
|||||||
class="py-8 px-4"
|
class="py-8 px-4"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'members'" label="Members">
|
<HoppSmartTab :id="'members'" :label="t('teams.team_members')">
|
||||||
<TeamsMembers @update-team="updateTeam()" class="py-8 px-4" />
|
<TeamsMembers @update-team="updateTeam()" class="py-8 px-4" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab :id="'invites'" label="Invites">
|
<HoppSmartTab :id="'invites'" :label="t('teams.invites')">
|
||||||
<TeamsPendingInvites :editingTeamID="team.id" class="py-8 px-4" />
|
<TeamsPendingInvites :editingTeamID="team.id" class="py-8 px-4" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
|
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmDeletion"
|
:show="confirmDeletion"
|
||||||
:title="`Confirm Deletion of ${team.name} team?`"
|
:title="t('teams.confirm_team_deletion')"
|
||||||
@hide-modal="confirmDeletion = false"
|
@hide-modal="confirmDeletion = false"
|
||||||
@resolve="deleteTeamMutation(deleteTeamUID)"
|
@resolve="deleteTeamMutation(deleteTeamUID)"
|
||||||
/>
|
/>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
import { useClientHandle, useMutation } from '@urql/vue';
|
import { useClientHandle, useMutation } from '@urql/vue';
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useToast } from '../../composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import {
|
import {
|
||||||
RemoveTeamDocument,
|
RemoveTeamDocument,
|
||||||
RenameTeamDocument,
|
RenameTeamDocument,
|
||||||
@@ -67,6 +67,9 @@ import {
|
|||||||
TeamInfoQuery,
|
TeamInfoQuery,
|
||||||
} from '../../helpers/backend/graphql';
|
} from '../../helpers/backend/graphql';
|
||||||
import { HoppSmartTabs } from '@hoppscotch/ui';
|
import { HoppSmartTabs } from '@hoppscotch/ui';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -77,11 +80,11 @@ const selectedOptionTab = ref<OptionTabs>('details');
|
|||||||
const currentTabName = computed(() => {
|
const currentTabName = computed(() => {
|
||||||
switch (selectedOptionTab.value) {
|
switch (selectedOptionTab.value) {
|
||||||
case 'details':
|
case 'details':
|
||||||
return 'Team details';
|
return t('teams.team_details');
|
||||||
case 'members':
|
case 'members':
|
||||||
return 'Team members';
|
return t('teams.team_members_tab');
|
||||||
case 'invites':
|
case 'invites':
|
||||||
return 'Pending invites';
|
return t('teams.pending_invites');
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -100,7 +103,7 @@ const getTeamInfo = async () => {
|
|||||||
.query(TeamInfoDocument, { teamID: route.params.id.toString() })
|
.query(TeamInfoDocument, { teamID: route.params.id.toString() })
|
||||||
.toPromise();
|
.toPromise();
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
return toast.error('Unable to load team info..');
|
return toast.error(`${t('team.load_info_error')}`);
|
||||||
}
|
}
|
||||||
if (result.data?.admin.teamInfo) {
|
if (result.data?.admin.teamInfo) {
|
||||||
team.value = result.data.admin.teamInfo;
|
team.value = result.data.admin.teamInfo;
|
||||||
@@ -127,12 +130,12 @@ const renameTeamName = async (teamName: string) => {
|
|||||||
const variables = { uid: team.value.id, name: teamName };
|
const variables = { uid: team.value.id, name: teamName };
|
||||||
await teamRename.executeMutation(variables).then((result) => {
|
await teamRename.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to rename team!!');
|
toast.error(`${t('state.rename_team_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
showRenameInput.value = false;
|
showRenameInput.value = false;
|
||||||
if (team.value) {
|
if (team.value) {
|
||||||
team.value.name = teamName;
|
team.value.name = teamName;
|
||||||
toast.success('Team renamed successfully!!');
|
toast.success(`${t('state.rename_team_success')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -152,15 +155,15 @@ const deleteTeam = (id: string) => {
|
|||||||
const deleteTeamMutation = async (id: string | null) => {
|
const deleteTeamMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
toast.error('Team deletion failed!!');
|
toast.error(`${t('state.delete_team_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await teamDeletion.executeMutation(variables).then((result) => {
|
await teamDeletion.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Team deletion failed!!');
|
toast.error(`${t('state.delete_team_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('Team deleted successfully!!');
|
toast.success(`${t('state.delete_team_success')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-lg font-bold text-secondaryDark">Teams</h1>
|
<h1 class="text-lg font-bold text-secondaryDark">{{ t('teams.teams') }}</h1>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex py-10">
|
<div class="flex py-10">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
:icon="IconAddUsers"
|
:icon="IconAddUsers"
|
||||||
label="Create team"
|
:label="t('teams.create_team')"
|
||||||
@click="showCreateTeamModal = true"
|
@click="showCreateTeamModal = true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<HoppSmartSpinner />
|
<HoppSmartSpinner />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error">Unable to Load Teams List..</div>
|
<div v-else-if="error">{{ t('teams.load_list_error') }}</div>
|
||||||
|
|
||||||
<TeamsTable
|
<TeamsTable
|
||||||
v-else
|
v-else
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
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"
|
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>Show more </span>
|
<span>{{ t('teams.show_more') }}</span>
|
||||||
<icon-lucide-chevron-down class="ml-2 text-lg" />
|
<icon-lucide-chevron-down class="ml-2 text-lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmDeletion"
|
:show="confirmDeletion"
|
||||||
:title="`Confirm Deletion of the team?`"
|
:title="t('teams.confirm_team_deletion')"
|
||||||
@hide-modal="confirmDeletion = false"
|
@hide-modal="confirmDeletion = false"
|
||||||
@resolve="deleteTeamMutation(deleteTeamID)"
|
@resolve="deleteTeamMutation(deleteTeamID)"
|
||||||
/>
|
/>
|
||||||
@@ -65,11 +65,14 @@ import {
|
|||||||
TeamListDocument,
|
TeamListDocument,
|
||||||
UsersListDocument,
|
UsersListDocument,
|
||||||
} from '../../helpers/backend/graphql';
|
} from '../../helpers/backend/graphql';
|
||||||
import { usePagedQuery } from '../../composables/usePagedQuery';
|
import { usePagedQuery } from '~/composables/usePagedQuery';
|
||||||
import { ref, watch, computed } from 'vue';
|
import { ref, watch, computed } from 'vue';
|
||||||
import { useMutation, useQuery } from '@urql/vue';
|
import { useMutation, useQuery } from '@urql/vue';
|
||||||
import { useToast } from '../../composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import IconAddUsers from '~icons/lucide/plus';
|
import IconAddUsers from '~icons/lucide/plus';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
// Get Users List
|
// Get Users List
|
||||||
@@ -110,11 +113,11 @@ const createTeamLoading = ref(false);
|
|||||||
|
|
||||||
const createTeam = async (newTeamName: string, ownerEmail: string) => {
|
const createTeam = async (newTeamName: string, ownerEmail: string) => {
|
||||||
if (newTeamName.length < 6) {
|
if (newTeamName.length < 6) {
|
||||||
toast.error('Team name should be atleast 6 characters long!!');
|
toast.error(`${t('state.team_name_long')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ownerEmail.length == 0) {
|
if (ownerEmail.length == 0) {
|
||||||
toast.error('Please enter email of team owner!!');
|
toast.error(`${t('state.enter_team_email')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createTeamLoading.value = true;
|
createTeamLoading.value = true;
|
||||||
@@ -124,13 +127,13 @@ const createTeam = async (newTeamName: string, ownerEmail: string) => {
|
|||||||
await createTeamMutation.executeMutation(variables).then((result) => {
|
await createTeamMutation.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
if (result.error.toString() == '[GraphQL] user/not_found') {
|
if (result.error.toString() == '[GraphQL] user/not_found') {
|
||||||
toast.error('User not found!!');
|
toast.error(`${t('state.user_not_found')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.error('Failed to create team!!');
|
toast.error(`${t('state.create_team_failure')}`);
|
||||||
}
|
}
|
||||||
createTeamLoading.value = false;
|
createTeamLoading.value = false;
|
||||||
} else {
|
} else {
|
||||||
toast.success('Team created successfully!!');
|
toast.success(`${t('state.create_team_success')}`);
|
||||||
showCreateTeamModal.value = false;
|
showCreateTeamModal.value = false;
|
||||||
createTeamLoading.value = false;
|
createTeamLoading.value = false;
|
||||||
refetch();
|
refetch();
|
||||||
@@ -164,16 +167,16 @@ const deleteTeam = (id: string) => {
|
|||||||
const deleteTeamMutation = async (id: string | null) => {
|
const deleteTeamMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
toast.error('Team deletion failed!!');
|
toast.error(`${t('state.delete_team_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await teamDeletion.executeMutation(variables).then((result) => {
|
await teamDeletion.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Team deletion failed!!');
|
toast.error(`${t('state.delete_team_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
teamList.value = teamList.value.filter((team) => team.id !== id);
|
teamList.value = teamList.value.filter((team) => team.id !== id);
|
||||||
toast.success('Team deleted successfully!!');
|
toast.success(`${t('state.delete_team_success')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
v-if="user.isAdmin"
|
v-if="user.isAdmin"
|
||||||
class="absolute left-17 bottom-0 text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
class="absolute left-17 bottom-0 text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
||||||
>
|
>
|
||||||
Admin
|
{{ t('users.admin') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -28,12 +28,14 @@
|
|||||||
v-if="user.isAdmin"
|
v-if="user.isAdmin"
|
||||||
class="absolute left-15 bottom-0 text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
class="absolute left-15 bottom-0 text-xs font-medium px-3 py-0.5 rounded-full bg-green-900 text-green-300"
|
||||||
>
|
>
|
||||||
Admin
|
{{ t('users.admin') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="user.uid">
|
<div v-if="user.uid">
|
||||||
<label class="text-secondaryDark" for="username">UID</label>
|
<label class="text-secondaryDark" for="username">{{
|
||||||
|
t('users.uid')
|
||||||
|
}}</label>
|
||||||
<div
|
<div
|
||||||
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
||||||
>
|
>
|
||||||
@@ -41,18 +43,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="text-secondaryDark" for="username">Name</label>
|
<label class="text-secondaryDark" for="username">{{
|
||||||
|
t('users.name')
|
||||||
|
}}</label>
|
||||||
<div
|
<div
|
||||||
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
||||||
>
|
>
|
||||||
<span v-if="user.displayName">
|
<span v-if="user.displayName">
|
||||||
{{ user.displayName }}
|
{{ user.displayName }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else> (Unnamed user) </span>
|
<span v-else> {{ t('users.unnamed') }} </span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.email">
|
<div v-if="user.email">
|
||||||
<label class="text-secondaryDark" for="username">Email</label>
|
<label class="text-secondaryDark" for="username">{{
|
||||||
|
t('users.email')
|
||||||
|
}}</label>
|
||||||
<div
|
<div
|
||||||
class="w-full p-3 mt-2 bg-zinc-800 border-gray-200 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
class="w-full p-3 mt-2 bg-zinc-800 border-gray-200 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
||||||
>
|
>
|
||||||
@@ -60,7 +66,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.createdOn">
|
<div v-if="user.createdOn">
|
||||||
<label class="text-secondaryDark" for="username">Created On</label>
|
<label class="text-secondaryDark" for="username">{{
|
||||||
|
t('users.created_on')
|
||||||
|
}}</label>
|
||||||
<div
|
<div
|
||||||
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
class="w-full p-3 mt-2 bg-zinc-800 border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
|
||||||
>
|
>
|
||||||
@@ -75,7 +83,7 @@
|
|||||||
class="mr-4"
|
class="mr-4"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
label="Make Admin"
|
:label="t('users.make_admin')"
|
||||||
@click="makeUserAdmin(user.uid)"
|
@click="makeUserAdmin(user.uid)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -85,7 +93,7 @@
|
|||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
:icon="IconUserMinus"
|
:icon="IconUserMinus"
|
||||||
label="Remove Admin Privilege"
|
:label="t('users.remove_admin_privilege')"
|
||||||
@click="makeAdminToUser(user.uid)"
|
@click="makeAdminToUser(user.uid)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -94,7 +102,7 @@
|
|||||||
class="mr-4 !bg-red-600 !text-gray-300 !hover:text-gray-100"
|
class="mr-4 !bg-red-600 !text-gray-300 !hover:text-gray-100"
|
||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
label="Delete"
|
:label="t('users.delete')"
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
@click="deleteUser(user.uid)"
|
@click="deleteUser(user.uid)"
|
||||||
/>
|
/>
|
||||||
@@ -105,26 +113,26 @@
|
|||||||
filled
|
filled
|
||||||
outline
|
outline
|
||||||
:icon="IconTrash"
|
:icon="IconTrash"
|
||||||
label="Delete"
|
:label="t('users.delete')"
|
||||||
@click="toast.error('Remove admin privilege to delete the user!!')"
|
@click="toast.error(t('state.remove_admin_to_delete_user'))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmDeletion"
|
:show="confirmDeletion"
|
||||||
:title="`Confirm deletion of user?`"
|
: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="`Do you want to make this user into an 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="`Do you want to remove admin status from this user?`"
|
:title="t('users.confirm_admin_to_user')"
|
||||||
@hide-modal="confirmAdminToUser = false"
|
@hide-modal="confirmAdminToUser = false"
|
||||||
@resolve="makeAdminToUserMutation(adminToUserUID)"
|
@resolve="makeAdminToUserMutation(adminToUserUID)"
|
||||||
/>
|
/>
|
||||||
@@ -143,9 +151,12 @@ import {
|
|||||||
import { useClientHandle } from '@urql/vue';
|
import { useClientHandle } from '@urql/vue';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useToast } from '../../composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import IconTrash from '~icons/lucide/trash';
|
import IconTrash from '~icons/lucide/trash';
|
||||||
import IconUserMinus from '~icons/lucide/user-minus';
|
import IconUserMinus from '~icons/lucide/user-minus';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -166,7 +177,7 @@ onMounted(async () => {
|
|||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Unable to load user info..');
|
toast.error(`${t('users.load_info_error')}`);
|
||||||
}
|
}
|
||||||
user.value = result.data?.admin.userInfo ?? {};
|
user.value = result.data?.admin.userInfo ?? {};
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
@@ -186,15 +197,15 @@ const deleteUser = (id: string) => {
|
|||||||
const deleteUserMutation = async (id: string | null) => {
|
const deleteUserMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
toast.error('User deletion failed!!');
|
toast.error(`${t('state.delete_user_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await userDeletion.executeMutation(variables).then((result) => {
|
await userDeletion.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('User deletion failed!!');
|
toast.error(`${t('state.delete_user_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('User deleted successfully!!');
|
toast.success(`${t('state.delete_user_success')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
@@ -215,16 +226,16 @@ const makeUserAdmin = (id: string) => {
|
|||||||
const makeUserAdminMutation = async (id: string | null) => {
|
const makeUserAdminMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmUserToAdmin.value = false;
|
confirmUserToAdmin.value = false;
|
||||||
toast.error('User deletion failed!!');
|
toast.error(`${t('state.admin_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await userToAdmin.executeMutation(variables).then((result) => {
|
await userToAdmin.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to make user an admin!!');
|
toast.error(`${t('state.admin_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
user.value.isAdmin = true;
|
user.value.isAdmin = true;
|
||||||
toast.success('User is now an admin!!');
|
toast.success(`${t('state.admin_success')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmUserToAdmin.value = false;
|
confirmUserToAdmin.value = false;
|
||||||
@@ -244,16 +255,16 @@ const makeAdminToUser = (id: string) => {
|
|||||||
const makeAdminToUserMutation = async (id: string | null) => {
|
const makeAdminToUserMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmAdminToUser.value = false;
|
confirmAdminToUser.value = false;
|
||||||
toast.error('Failed to remove admin status!!');
|
toast.error(`${t('state.remove_admin_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await adminToUser.executeMutation(variables).then((result) => {
|
await adminToUser.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to remove admin status!!');
|
toast.error(`${t('state.remove_admin_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
user.value.isAdmin = false;
|
user.value.isAdmin = false;
|
||||||
toast.success('Admin status removed!!');
|
toast.error(`${t('state.remove_admin_success')}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
confirmAdminToUser.value = false;
|
confirmAdminToUser.value = false;
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<!-- Table View for All Users -->
|
<!-- Table View for All Users -->
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1 class="text-lg font-bold text-secondaryDark">Users</h1>
|
<h1 class="text-lg font-bold text-secondaryDark">
|
||||||
|
{{ t('users.users') }}
|
||||||
|
</h1>
|
||||||
<div class="flex items-center space-x-4 py-10">
|
<div class="flex items-center space-x-4 py-10">
|
||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
label="Invite a user"
|
:label="t('users.invite_user')"
|
||||||
@click="showInviteUserModal = true"
|
@click="showInviteUserModal = true"
|
||||||
:icon="IconAddUser"
|
:icon="IconAddUser"
|
||||||
/>
|
/>
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
outline
|
outline
|
||||||
filled
|
filled
|
||||||
label="Invited users"
|
:label="t('users.invited_users')"
|
||||||
:to="'/users/invited'"
|
:to="'/users/invited'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
<HoppSmartSpinner />
|
<HoppSmartSpinner />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="error">Unable to Load Users List..</div>
|
<div v-else-if="error">{{ t('users.load_list_error') }}</div>
|
||||||
|
|
||||||
<UsersTable
|
<UsersTable
|
||||||
v-else-if="usersList.length >= 1"
|
v-else-if="usersList.length >= 1"
|
||||||
@@ -40,14 +42,14 @@
|
|||||||
@deleteUser="deleteUser"
|
@deleteUser="deleteUser"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-else class="flex justify-center">No Users Found</div>
|
<div v-else class="flex justify-center">{{ t('users.no_users') }}</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="hasNextPage && usersList.length >= usersPerPage"
|
v-if="hasNextPage && usersList.length >= usersPerPage"
|
||||||
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"
|
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"
|
@click="fetchNextUsers"
|
||||||
>
|
>
|
||||||
<span>Show more </span>
|
<span>{{ t('users.show_more') }}</span>
|
||||||
<icon-lucide-chevron-down class="ml-2 text-lg" />
|
<icon-lucide-chevron-down class="ml-2 text-lg" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,19 +62,19 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartConfirmModal
|
<HoppSmartConfirmModal
|
||||||
:show="confirmDeletion"
|
:show="confirmDeletion"
|
||||||
:title="`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="`Do you want to make this user into an 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="`Do you want to remove admin status from this user?`"
|
:title="t('users.confirm_admin_to_user')"
|
||||||
@hide-modal="confirmAdminToUser = false"
|
@hide-modal="confirmAdminToUser = false"
|
||||||
@resolve="makeAdminToUserMutation(adminToUserUID)"
|
@resolve="makeAdminToUserMutation(adminToUserUID)"
|
||||||
/>
|
/>
|
||||||
@@ -89,11 +91,14 @@ import {
|
|||||||
RemoveUserAsAdminDocument,
|
RemoveUserAsAdminDocument,
|
||||||
UsersListDocument,
|
UsersListDocument,
|
||||||
} from '../../helpers/backend/graphql';
|
} from '../../helpers/backend/graphql';
|
||||||
import { usePagedQuery } from '../../composables/usePagedQuery';
|
import { usePagedQuery } from '~/composables/usePagedQuery';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useToast } from '../../composables/toast';
|
import { useToast } from '~/composables/toast';
|
||||||
import { HoppButtonSecondary } from '@hoppscotch/ui';
|
import { HoppButtonSecondary } from '@hoppscotch/ui';
|
||||||
import IconAddUser from '~icons/lucide/user-plus';
|
import IconAddUser from '~icons/lucide/user-plus';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
@@ -119,15 +124,15 @@ const showInviteUserModal = ref(false);
|
|||||||
|
|
||||||
const sendInvite = async (email: string) => {
|
const sendInvite = async (email: string) => {
|
||||||
if (!email.trim()) {
|
if (!email.trim()) {
|
||||||
toast.error('Please enter a valid email address');
|
toast.error(`${t('state.invalid_email')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { inviteeEmail: email.trim() };
|
const variables = { inviteeEmail: email.trim() };
|
||||||
await sendInvitation.executeMutation(variables).then((result) => {
|
await sendInvitation.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to send invitation');
|
toast.error(`${t('state.email_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('Email invitation sent successfully');
|
toast.success(`${t('state.email_success')}`);
|
||||||
showInviteUserModal.value = false;
|
showInviteUserModal.value = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -153,15 +158,15 @@ const deleteUserUID = ref<string | null>(null);
|
|||||||
const deleteUserMutation = async (id: string | null) => {
|
const deleteUserMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmDeletion.value = false;
|
confirmDeletion.value = false;
|
||||||
toast.error('User deletion failed!!');
|
toast.error(`${t('state.delete_user_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await userDeletion.executeMutation(variables).then((result) => {
|
await userDeletion.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('User deletion failed!!');
|
toast.error(`${t('state.delete_user_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('User deleted successfully!!');
|
toast.success(`${t('state.delete_user_success')}`);
|
||||||
usersList.value = usersList.value.filter((user) => user.uid !== id);
|
usersList.value = usersList.value.filter((user) => user.uid !== id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -182,15 +187,15 @@ const makeUserAdmin = (id: string) => {
|
|||||||
const makeUserAdminMutation = async (id: string | null) => {
|
const makeUserAdminMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmUserToAdmin.value = false;
|
confirmUserToAdmin.value = false;
|
||||||
toast.error('Failed to make user an admin!!');
|
toast.error(`${t('state.admin_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await userToAdmin.executeMutation(variables).then((result) => {
|
await userToAdmin.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to make user an admin!!');
|
toast.error(`${t('state.admin_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('User is now an admin!!');
|
toast.success(`${t('state.admin_success')}`);
|
||||||
usersList.value = usersList.value.map((user) => {
|
usersList.value = usersList.value.map((user) => {
|
||||||
if (user.uid === id) {
|
if (user.uid === id) {
|
||||||
user.isAdmin = true;
|
user.isAdmin = true;
|
||||||
@@ -221,15 +226,15 @@ const deleteUser = (id: string) => {
|
|||||||
const makeAdminToUserMutation = async (id: string | null) => {
|
const makeAdminToUserMutation = async (id: string | null) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
confirmAdminToUser.value = false;
|
confirmAdminToUser.value = false;
|
||||||
toast.error('Failed to remove admin status!!');
|
toast.error(`${t('state.remove_admin_failure')}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const variables = { uid: id };
|
const variables = { uid: id };
|
||||||
await adminToUser.executeMutation(variables).then((result) => {
|
await adminToUser.executeMutation(variables).then((result) => {
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
toast.error('Failed to remove admin status!!');
|
toast.error(`${t('state.remove_admin_failure')}`);
|
||||||
} else {
|
} else {
|
||||||
toast.success('Admin status removed!!');
|
toast.success(`${t('state.remove_admin_success')}`);
|
||||||
usersList.value = usersList.value.map((user) => {
|
usersList.value = usersList.value.map((user) => {
|
||||||
if (user.uid === id) {
|
if (user.uid === id) {
|
||||||
user.isAdmin = false;
|
user.isAdmin = false;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="text-lg font-bold text-accentContrast py-6">Invited Users</h3>
|
<h3 class="text-lg font-bold text-accentContrast py-6">
|
||||||
|
{{ t('users.invited_users') }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="py-2 overflow-x-auto">
|
<div class="py-2 overflow-x-auto">
|
||||||
@@ -15,7 +17,7 @@
|
|||||||
<HoppSmartSpinner />
|
<HoppSmartSpinner />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="error || invitedUsers === undefined">
|
<div v-else-if="error || invitedUsers === undefined">
|
||||||
<p class="text-xl">No invited users found..</p>
|
<p class="text-xl">{{ t('users.no_invite') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table v-else class="w-full text-left">
|
<table v-else class="w-full text-left">
|
||||||
@@ -23,10 +25,10 @@
|
|||||||
<tr
|
<tr
|
||||||
class="text-secondary border-b border-dividerDark text-sm text-left"
|
class="text-secondary border-b border-dividerDark text-sm text-left"
|
||||||
>
|
>
|
||||||
<th class="px-3 pb-3">Admin ID</th>
|
<th class="px-3 pb-3">{{ t('users.admin_id') }}</th>
|
||||||
<th class="px-3 pb-3">Admin Email</th>
|
<th class="px-3 pb-3">{{ t('users.admin_email') }}</th>
|
||||||
<th class="px-3 pb-3">Invitee Email</th>
|
<th class="px-3 pb-3">{{ t('users.invitee_email') }}</th>
|
||||||
<th class="px-3 pb-3">Invited On</th>
|
<th class="px-3 pb-3">{{ t('users.invited_on') }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-divider">
|
<tbody class="divide-y divide-divider">
|
||||||
@@ -34,7 +36,7 @@
|
|||||||
v-if="invitedUsers.length === 0"
|
v-if="invitedUsers.length === 0"
|
||||||
class="text-secondaryDark py-4"
|
class="text-secondaryDark py-4"
|
||||||
>
|
>
|
||||||
<div class="py-6 px-3">No invited users found..</div>
|
<div class="py-6 px-3">{{ t('users.no_invite') }}</div>
|
||||||
</tr>
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
v-else
|
v-else
|
||||||
@@ -85,6 +87,9 @@ import { InvitedUsersDocument } from '../../helpers/backend/graphql';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { HoppSmartSpinner } from '@hoppscotch/ui';
|
import { HoppSmartSpinner } from '@hoppscotch/ui';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from '~/composables/i18n';
|
||||||
|
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Components from 'unplugin-vue-components/vite';
|
|||||||
import WindiCSS from 'vite-plugin-windicss';
|
import WindiCSS from 'vite-plugin-windicss';
|
||||||
import Pages from 'vite-plugin-pages';
|
import Pages from 'vite-plugin-pages';
|
||||||
import Layouts from 'vite-plugin-vue-layouts';
|
import Layouts from 'vite-plugin-vue-layouts';
|
||||||
|
import VueI18n from '@intlify/vite-plugin-vue-i18n';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
@@ -31,6 +32,11 @@ export default defineConfig({
|
|||||||
defaultLayout: 'default',
|
defaultLayout: 'default',
|
||||||
layoutsDirs: 'src/layouts',
|
layoutsDirs: 'src/layouts',
|
||||||
}),
|
}),
|
||||||
|
VueI18n({
|
||||||
|
runtimeOnly: false,
|
||||||
|
compositionOnly: true,
|
||||||
|
include: [path.resolve(__dirname, 'locales')],
|
||||||
|
}),
|
||||||
WindiCSS({
|
WindiCSS({
|
||||||
root: path.resolve(__dirname),
|
root: path.resolve(__dirname),
|
||||||
}),
|
}),
|
||||||
|
|||||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
@@ -917,7 +917,7 @@ importers:
|
|||||||
version: 3.1.1(graphql@15.8.0)
|
version: 3.1.1(graphql@15.8.0)
|
||||||
'@intlify/vite-plugin-vue-i18n':
|
'@intlify/vite-plugin-vue-i18n':
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.0(vite@3.1.4)(vue-i18n@9.2.2)
|
version: 7.0.0(vite@3.2.4)(vue-i18n@9.2.2)
|
||||||
'@rushstack/eslint-patch':
|
'@rushstack/eslint-patch':
|
||||||
specifier: ^1.1.4
|
specifier: ^1.1.4
|
||||||
version: 1.1.4
|
version: 1.1.4
|
||||||
@@ -1068,6 +1068,9 @@ importers:
|
|||||||
vue:
|
vue:
|
||||||
specifier: ^3.2.6
|
specifier: ^3.2.6
|
||||||
version: 3.2.45
|
version: 3.2.45
|
||||||
|
vue-i18n:
|
||||||
|
specifier: ^9.2.2
|
||||||
|
version: 9.2.2(vue@3.2.45)
|
||||||
vue-router:
|
vue-router:
|
||||||
specifier: '4'
|
specifier: '4'
|
||||||
version: 4.1.0(vue@3.2.45)
|
version: 4.1.0(vue@3.2.45)
|
||||||
@@ -1099,6 +1102,9 @@ importers:
|
|||||||
'@graphql-codegen/urql-introspection':
|
'@graphql-codegen/urql-introspection':
|
||||||
specifier: 2.2.1
|
specifier: 2.2.1
|
||||||
version: 2.2.1(graphql@16.6.0)
|
version: 2.2.1(graphql@16.6.0)
|
||||||
|
'@intlify/vite-plugin-vue-i18n':
|
||||||
|
specifier: ^7.0.0
|
||||||
|
version: 7.0.0(vite@3.2.4)(vue-i18n@9.2.2)
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.2.0(vite@3.2.4)(vue@3.2.45)
|
version: 3.2.0(vite@3.2.4)(vue@3.2.45)
|
||||||
@@ -5765,6 +5771,34 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@intlify/vite-plugin-vue-i18n@7.0.0(vite@3.2.4)(vue-i18n@9.2.2):
|
||||||
|
resolution: {integrity: sha512-2TbDOQ8XD+vkc0s5OFmr+IY/k4mYMC7pzvx0xGQn+cU/ev314+yi7Z7N7rWcBgiYk1WOUalbGSo3d4nJDxOOyw==}
|
||||||
|
engines: {node: '>= 14.6'}
|
||||||
|
deprecated: This plugin support until Vite 3. If you would like to use on Vite 4, please use @intlify/unplugin-vue-i18n
|
||||||
|
peerDependencies:
|
||||||
|
petite-vue-i18n: '*'
|
||||||
|
vite: ^2.9.0 || ^3.0.0
|
||||||
|
vue-i18n: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
petite-vue-i18n:
|
||||||
|
optional: true
|
||||||
|
vite:
|
||||||
|
optional: true
|
||||||
|
vue-i18n:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
|
||||||
|
'@intlify/shared': 9.3.0-beta.17
|
||||||
|
'@rollup/pluginutils': 4.2.1
|
||||||
|
debug: 4.3.4(supports-color@9.2.2)
|
||||||
|
fast-glob: 3.2.12
|
||||||
|
source-map: 0.6.1
|
||||||
|
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)
|
||||||
|
vue-i18n: 9.2.2(vue@3.2.45)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@intlify/vue-devtools@9.2.2:
|
/@intlify/vue-devtools@9.2.2:
|
||||||
resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==}
|
resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
@@ -9169,7 +9203,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/after@0.8.2:
|
/after@0.8.2:
|
||||||
resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=}
|
resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/agent-base@6.0.2:
|
/agent-base@6.0.2:
|
||||||
@@ -9781,7 +9815,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
/base64-arraybuffer@0.1.4:
|
/base64-arraybuffer@0.1.4:
|
||||||
resolution: {integrity: sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=}
|
resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -10428,14 +10462,14 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/component-bind@1.0.0:
|
/component-bind@1.0.0:
|
||||||
resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=}
|
resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/component-emitter@1.3.0:
|
/component-emitter@1.3.0:
|
||||||
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
||||||
|
|
||||||
/component-inherit@0.0.3:
|
/component-inherit@0.0.3:
|
||||||
resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=}
|
resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/concat-map@0.0.1:
|
/concat-map@0.0.1:
|
||||||
@@ -13635,7 +13669,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-cors@1.1.0:
|
/has-cors@1.1.0:
|
||||||
resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=}
|
resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-flag@3.0.0:
|
/has-flag@3.0.0:
|
||||||
@@ -14028,7 +14062,7 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/indexof@0.0.1:
|
/indexof@0.0.1:
|
||||||
resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=}
|
resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/inflight@1.0.6:
|
/inflight@1.0.6:
|
||||||
@@ -19743,7 +19777,7 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/to-array@0.1.4:
|
/to-array@0.1.4:
|
||||||
resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=}
|
resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/to-fast-properties@2.0.0:
|
/to-fast-properties@2.0.0:
|
||||||
@@ -21458,6 +21492,18 @@ packages:
|
|||||||
'@vue/devtools-api': 6.2.1
|
'@vue/devtools-api': 6.2.1
|
||||||
vue: 3.2.37
|
vue: 3.2.37
|
||||||
|
|
||||||
|
/vue-i18n@9.2.2(vue@3.2.45):
|
||||||
|
resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
peerDependencies:
|
||||||
|
vue: ^3.0.0
|
||||||
|
dependencies:
|
||||||
|
'@intlify/core-base': 9.2.2
|
||||||
|
'@intlify/shared': 9.2.2
|
||||||
|
'@intlify/vue-devtools': 9.2.2
|
||||||
|
'@vue/devtools-api': 6.2.1
|
||||||
|
vue: 3.2.45
|
||||||
|
|
||||||
/vue-loader@16.8.3(@vue/compiler-sfc@3.2.45)(vue@3.2.45)(webpack@5.74.0):
|
/vue-loader@16.8.3(@vue/compiler-sfc@3.2.45)(vue@3.2.45)(webpack@5.74.0):
|
||||||
resolution: {integrity: sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==}
|
resolution: {integrity: sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -22358,7 +22404,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yeast@0.1.2:
|
/yeast@0.1.2:
|
||||||
resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=}
|
resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yn@3.1.1:
|
/yn@3.1.1:
|
||||||
|
|||||||
Reference in New Issue
Block a user