Просмотр исходного кода

feat: introducing user guidance and error management helpers in admin dashboard (#4548)

Joel Jacob Stephen 3 месяцев назад
Родитель
Сommit
73f3e54c00

+ 8 - 1
packages/hoppscotch-sh-admin/locales/en.json

@@ -96,7 +96,7 @@
     "last_used_on": "Last used on",
     "no_expiration": "No expiration",
     "no_expiration_verbose": "This token will never expire!",
-    "section_description": "Manage your Hoppscotch users through APIs with Infra tokens",
+    "section_description": "Manage your Hoppscotch users through APIs with Infra tokens.",
     "section_title": "Infra Tokens",
     "tab_title": "Infra Tokens",
     "token_expires_on": "This token will expire on",
@@ -247,6 +247,11 @@
     "users_to_admin_success": "Selected users are elevated to admin status!!",
     "users_to_admin_failure": "Failed to elevate selected users to admin status!!"
   },
+  "support": {
+    "description": "Get help from the Hoppscotch community",
+    "documentation": "Documentation",
+    "more_info": "More Info"
+  },
   "teams": {
     "add_member": "Add Member",
     "add_members": "Add Members",
@@ -325,6 +330,7 @@
     "invalid_user": "Invalid User",
     "invite_load_list_error": "Unable to Load Invited Users List",
     "invite_user": "Invite User",
+    "invite_users_description": "Invite your team members to join Hoppscotch.",
     "invited_by": "Invited By",
     "invited_on": "Invited On",
     "invited_users": "Invited Users",
@@ -341,6 +347,7 @@
     "not_available": "Not Available",
     "not_found": "User not found",
     "pending_invites": "Pending Invites",
+    "pending_invites_description": "Manage and track pending user invitations with clear status and actions.",
     "remove_admin_privilege": "Remove Admin Privilege",
     "remove_admin_status": "Remove Admin Status",
     "rename": "Rename",

+ 2 - 0
packages/hoppscotch-sh-admin/src/components.d.ts

@@ -14,6 +14,7 @@ declare module 'vue' {
     AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
     AppToast: typeof import('./components/app/Toast.vue')['default']
     DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
+    FallbackComponent: typeof import('./components/FallbackComponent.vue')['default']
     HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
     HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
     HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
@@ -34,6 +35,7 @@ declare module 'vue' {
     HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
     HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
     IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
+    IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
     IconLucideCheck: typeof import('~icons/lucide/check')['default']
     IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
     IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']

+ 11 - 0
packages/hoppscotch-sh-admin/src/components/app/Header.vue

@@ -24,6 +24,16 @@
     </div>
 
     <div class="flex items-center">
+      <div class="inline-flex items-center mr-5">
+        <HoppButtonSecondary
+          to="https://docs.hoppscotch.io/documentation"
+          blank
+          v-tippy="{ theme: 'tooltip' }"
+          :title="t('support.documentation')"
+          :icon="IconHelpCircle"
+          class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
+        />
+      </div>
       <div v-if="currentUser" class="relative">
         <tippy
           interactive
@@ -69,6 +79,7 @@ import { useSidebar } from '~/composables/useSidebar';
 import { auth } from '~/helpers/auth';
 import IconMenu from '~icons/lucide/menu';
 import IconSidebarOpen from '~icons/lucide/sidebar-open';
+import IconHelpCircle from '~icons/lucide/help-circle';
 import IconSidebarClose from '~icons/lucide/sidebar-close';
 import { useI18n } from '~/composables/i18n';
 

+ 10 - 1
packages/hoppscotch-sh-admin/src/components/settings/AuthProvider.vue

@@ -17,13 +17,21 @@
           v-for="provider in workingConfigs.providers"
           class="space-y-4 py-4"
         >
-          <div class="flex items-center">
+          <div class="flex justify-between">
             <HoppSmartToggle
               :on="provider.enabled"
               @change="provider.enabled = !provider.enabled"
             >
               {{ capitalize(provider.name) }}
             </HoppSmartToggle>
+            <HoppButtonSecondary
+              v-tippy="{ theme: 'tooltip', allowHTML: true }"
+              to="https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites#oauth"
+              blank
+              :title="t('support.documentation')"
+              :icon="IconCircleHelp"
+              class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
+            />
           </div>
 
           <div v-if="provider.enabled" class="ml-12">
@@ -67,6 +75,7 @@ import { useVModel } from '@vueuse/core';
 import { reactive } from 'vue';
 import { useI18n } from '~/composables/i18n';
 import { ServerConfigs, SsoAuthProviders } from '~/helpers/configs';
+import IconCircleHelp from '~icons/lucide/circle-help';
 import IconEye from '~icons/lucide/eye';
 import IconEyeOff from '~icons/lucide/eye-off';
 

+ 20 - 6
packages/hoppscotch-sh-admin/src/components/settings/DataSharing.vue

@@ -13,12 +13,22 @@
       </h4>
 
       <div class="flex items-center space-y-4 py-4">
-        <HoppSmartToggle
-          :on="dataSharingConfigs.enabled"
-          @change="dataSharingConfigs.enabled = !dataSharingConfigs.enabled"
-        >
-          {{ t('configs.data_sharing.toggle_description') }}
-        </HoppSmartToggle>
+        <div class="flex justify-between w-full">
+          <HoppSmartToggle
+            :on="dataSharingConfigs.enabled"
+            @change="dataSharingConfigs.enabled = !dataSharingConfigs.enabled"
+          >
+            {{ t('configs.data_sharing.toggle_description') }}
+          </HoppSmartToggle>
+          <HoppButtonSecondary
+            v-tippy="{ theme: 'tooltip', allowHTML: true }"
+            blank
+            to="https://docs.hoppscotch.io/documentation/self-host/community-edition/telemetry"
+            :title="t('support.documentation')"
+            :icon="IconHelpCircle"
+            class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
+          />
+        </div>
       </div>
 
       <HoppButtonSecondary
@@ -30,6 +40,9 @@
         blank
         class="w-min my-2"
       />
+      <p class="my-1 text-secondaryLight">
+        {{ t('configs.data_sharing.description') }}
+      </p>
     </div>
   </div>
 </template>
@@ -40,6 +53,7 @@ import { computed } from 'vue';
 import { useI18n } from '~/composables/i18n';
 import { ServerConfigs } from '~/helpers/configs';
 import IconShieldQuestion from '~icons/lucide/shield-question';
+import IconHelpCircle from '~icons/lucide/help-circle';
 
 const t = useI18n();
 

+ 17 - 6
packages/hoppscotch-sh-admin/src/components/settings/SmtpConfiguration.vue

@@ -15,12 +15,22 @@
 
         <div class="space-y-4 py-4">
           <div class="flex items-center">
-            <HoppSmartToggle
-              :on="smtpConfigs.enabled"
-              @change="smtpConfigs.enabled = !smtpConfigs.enabled"
-            >
-              {{ t('configs.mail_configs.enable_smtp') }}
-            </HoppSmartToggle>
+            <div class="flex justify-between w-full">
+              <HoppSmartToggle
+                :on="smtpConfigs.enabled"
+                @change="smtpConfigs.enabled = !smtpConfigs.enabled"
+              >
+                {{ t('configs.mail_configs.enable_smtp') }}
+              </HoppSmartToggle>
+              <HoppButtonSecondary
+                blank
+                v-tippy="{ theme: 'tooltip', allowHTML: true }"
+                to="https://docs.hoppscotch.io/documentation/self-host/community-edition/prerequisites#email-delivery"
+                :title="t('support.documentation')"
+                :icon="IconHelpCircle"
+                class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
+              />
+            </div>
           </div>
 
           <div v-if="smtpConfigs.enabled" class="ml-12">
@@ -94,6 +104,7 @@ import { useI18n } from '~/composables/i18n';
 import { ServerConfigs } from '~/helpers/configs';
 import IconEye from '~icons/lucide/eye';
 import IconEyeOff from '~icons/lucide/eye-off';
+import IconHelpCircle from '~icons/lucide/help-circle';
 
 const t = useI18n();
 

+ 12 - 3
packages/hoppscotch-sh-admin/src/components/tokens/Overview.vue

@@ -5,9 +5,18 @@
         {{ t('infra_tokens.section_title') }}
       </h4>
 
-      <p class="text-secondaryLight">
-        {{ t('infra_tokens.section_description') }}
-      </p>
+      <div class="flex">
+        <p class="text-secondaryLight">
+          {{ t('infra_tokens.section_description') }}
+        </p>
+        <HoppSmartAnchor
+          blank
+          to="https://docs.hoppscotch.io/documentation/self-host/community-edition/admin-dashboard#infratokens"
+          :label="t('support.more_info')"
+          class="underline ml-1"
+        />
+        <icon-lucide-arrow-up-right class="underline w-4 h-4" />
+      </div>
     </div>
 
     <HoppButtonSecondary

+ 29 - 12
packages/hoppscotch-sh-admin/src/components/users/InviteModal.vue

@@ -13,18 +13,34 @@
       />
     </template>
     <template #footer>
-      <span class="flex space-x-2">
-        <HoppButtonPrimary
-          :label="t('users.add_user')"
-          @click="emit('send-invite', email)"
-        />
-        <HoppButtonSecondary
-          :label="t('users.cancel')"
-          outline
-          filled
-          @click="hideModal"
-        />
-      </span>
+      <div class="w-full">
+        <p class="text-secondaryLight mb-5 text-center">
+          {{ t('users.invite_users_description') }}
+        </p>
+
+        <div class="flex justify-between">
+          <HoppButtonSecondary
+            v-tippy="{ theme: 'tooltip', allowHTML: true }"
+            to="https://docs.hoppscotch.io/documentation"
+            blank
+            :title="t('support.documentation')"
+            :icon="IconCircleHelp"
+            class="rounded hover:bg-primaryDark focus-visible:bg-primaryDark"
+          />
+          <span class="flex space-x-2">
+            <HoppButtonPrimary
+              :label="t('users.add_user')"
+              @click="emit('send-invite', email)"
+            />
+            <HoppButtonSecondary
+              :label="t('users.cancel')"
+              outline
+              filled
+              @click="hideModal"
+            />
+          </span>
+        </div>
+      </div>
     </template>
   </HoppSmartModal>
 </template>
@@ -32,6 +48,7 @@
 <script setup lang="ts">
 import { ref } from 'vue';
 import { useI18n } from '~/composables/i18n';
+import IconCircleHelp from '~icons/lucide/circle-help';
 
 const t = useI18n();
 

+ 9 - 1
packages/hoppscotch-sh-admin/src/helpers/errors.ts

@@ -1,7 +1,15 @@
+/*
+ * Type used to send error data to the Fallback catch-all component
+ */
+export type ErrorPageData = {
+  message: string;
+  statusCode?: number;
+};
+
 /* No cookies were found in the auth request
  * (AuthService)
  */
-export const COOKIES_NOT_FOUND = 'auth/cookies_not_found' as const;
+export const COOKIES_NOT_FOUND = '[GraphQL] auth/cookies_not_found' as const;
 
 export const UNAUTHORIZED = 'Unauthorized' as const;
 

+ 50 - 41
packages/hoppscotch-sh-admin/src/main.ts

@@ -1,65 +1,74 @@
-import { createApp } from 'vue';
-import urql, { createClient, cacheExchange, fetchExchange } from '@urql/vue';
 import { authExchange } from '@urql/exchange-auth';
+import urql, { cacheExchange, createClient, fetchExchange } from '@urql/vue';
+import { createApp, h } from 'vue';
 import App from './App.vue';
+import ErrorComponent from './pages/_.vue';
 
 // STYLES
-import '@hoppscotch/ui/style.css';
-import '../assets/scss/styles.scss';
-import '../assets/scss/tailwind.scss';
 import '@fontsource-variable/inter';
 import '@fontsource-variable/material-symbols-rounded';
 import '@fontsource-variable/roboto-mono';
+import '@hoppscotch/ui/style.css';
+import '../assets/scss/styles.scss';
+import '../assets/scss/tailwind.scss';
 // END STYLES
 
-import { HOPP_MODULES } from './modules';
-import { auth } from './helpers/auth';
 import { pipe } from 'fp-ts/function';
 import * as O from 'fp-ts/Option';
+import { auth } from './helpers/auth';
 import { GRAPHQL_UNAUTHORIZED } from './helpers/errors';
+import { HOPP_MODULES } from './modules';
 
-// Top-level await is not available in our targets
 (async () => {
-  const app = createApp(App).use(
-    urql,
-    createClient({
+  try {
+    // Create URQL client
+    const urqlClient = createClient({
       url: import.meta.env.VITE_BACKEND_GQL_URL,
       requestPolicy: 'network-only',
-      fetchOptions: () => {
-        return {
-          credentials: 'include',
-        };
-      },
+      fetchOptions: () => ({
+        credentials: 'include',
+      }),
       exchanges: [
         cacheExchange,
-        authExchange(async () => {
-          return {
-            addAuthToOperation(operation) {
-              return operation;
-            },
-
-            async refreshAuth() {
-              pipe(
-                await auth.performAuthRefresh(),
-                O.getOrElseW(() => auth.signOutUser(true))
-              );
-            },
-
-            didAuthError(error, _operation) {
-              return error.message === GRAPHQL_UNAUTHORIZED;
-            },
-          };
-        }),
+        authExchange(async () => ({
+          addAuthToOperation(operation) {
+            return operation;
+          },
+          async refreshAuth() {
+            pipe(
+              await auth.performAuthRefresh(),
+              O.getOrElseW(() => auth.signOutUser(true))
+            );
+          },
+          didAuthError(error, _operation) {
+            return error.message === GRAPHQL_UNAUTHORIZED;
+          },
+        })),
         fetchExchange,
       ],
-    })
-  );
+    });
+
+    // Initialize auth
+    await auth.performAuthInit();
 
-  // Initialize auth
-  await auth.performAuthInit();
+    const app = createApp({
+      render: () => h(App),
+    }).use(urql, urqlClient);
 
-  // Initialize modules
-  HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app));
+    // Initialize modules
+    HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app));
 
-  app.mount('#app');
+    app.mount('#app');
+  } catch (error) {
+    // Mount the fallback component in case of an error
+    createApp({
+      render: () =>
+        h(ErrorComponent, {
+          error: {
+            message:
+              'Failed to connect to the backend server, make sure the backend is setup correctly',
+          },
+        }),
+    }).mount('#app');
+  }
 })();

Некоторые файлы не были показаны из-за большого количества измененных файлов