diff --git a/packages/hoppscotch-sh-admin/assets/images/hoppscotch-title.svg b/packages/hoppscotch-sh-admin/assets/images/hoppscotch-title.svg
new file mode 100644
index 00000000..b33ed65b
--- /dev/null
+++ b/packages/hoppscotch-sh-admin/assets/images/hoppscotch-title.svg
@@ -0,0 +1,16 @@
+
diff --git a/packages/hoppscotch-sh-admin/locales/en.json b/packages/hoppscotch-sh-admin/locales/en.json
index b95ca905..c2c4b156 100644
--- a/packages/hoppscotch-sh-admin/locales/en.json
+++ b/packages/hoppscotch-sh-admin/locales/en.json
@@ -1,10 +1,12 @@
{
"app": {
"collapse_sidebar": "Collapse Sidebar",
+ "continue_to_dashboard": "Continue to Dashboard",
"expand_sidebar": "Expand Sidebar",
"name": "HOPPSCOTCH",
"no_name": "No name",
- "open_navigation": "Open Navigation"
+ "open_navigation": "Open Navigation",
+ "read_documentation": "Read Documentation"
},
"configs": {
"auth_providers": {
@@ -16,6 +18,15 @@
},
"confirm_changes": "Hoppscotch server must restart to reflect the new changes. Confirm changes made to the server configurations?",
"input_empty": "Please fill all the fields before updating the configurations",
+ "data_sharing": {
+ "title": "Data Sharing",
+ "description": "Help improve Hoppscotch by sharing anonymous data",
+ "enable": "Enable Data Sharing",
+ "secondary_title": "Data Sharing Configurations",
+ "see_shared": "See what is shared",
+ "toggle_description": "Share anonymous data",
+ "update_failure": "Failed to update data sharing configurations!!"
+ },
"load_error": "Unable to load server configurations",
"mail_configs": {
"description": " Configure the smtp configurations",
@@ -40,6 +51,14 @@
"save_changes": "Save Changes",
"title": "Configurations"
},
+ "data_sharing": {
+ "description": "Share anonymous data usage to improve Hoppscotch",
+ "enable": "Enable Data Sharing",
+ "see_shared": "See what is shared",
+ "toggle_description": "Share data and make Hoppscotch better",
+ "title": "Make Hoppscotch Better",
+ "welcome": "Welcome to"
+ },
"metrics": {
"dashboard": "Dashboard",
"no_metrics": "No metrics found",
@@ -48,6 +67,13 @@
"total_teams": "Total Workspaces",
"total_users": "Total Users"
},
+ "newsletter": {
+ "description": "Get updates about our latest news",
+ "subscribe": "Subscribe",
+ "title": "Stay in Touch",
+ "toggle_description": "Get updates about the latest at Hoppscotch",
+ "unsubscribe": "Unsubscribe"
+ },
"settings": {
"settings": "Settings"
},
@@ -86,6 +112,7 @@
"copied_to_clipboard": "Copied to clipboard",
"create_team_failure": "Failed to create workspace!!",
"create_team_success": "Workspace created successfully!!",
+ "data_sharing_failure": "Failed to update data sharing settings",
"delete_request_failure": "Shared Request deletion failed!!",
"delete_request_success": "Shared Request deleted successfully!!",
"delete_team_failure": "Workspace deletion failed!!",
@@ -108,6 +135,7 @@
"magic_link_sign_in": "Click on the link to sign in.",
"magic_link_success": "We sent a magic link to",
"microsoft_signin_failure": "Failed to login with Microsoft",
+ "newsletter_failure": "Unable to update newsletter settings",
"non_admin_logged_in": "Logged in as non admin user.",
"non_admin_login": "You are logged in. But you're not an admin",
"owner_not_present": "Atleast one owner should be present in the team!!",
@@ -127,10 +155,12 @@
"role_update_success": "Roles updated successfully!!",
"self_host_docs": "Self Host Documentation",
"send_magic_link": "Send magic link",
+ "setup_failure": "Setup has failed!!",
+ "setup_success": "Setup completed successfully!!",
"sign_in_agreement": "By signing in, you are agreeing to our",
"sign_in_options": "All sign in option",
"sign_out": "Sign out",
- "team_name_long": "Workspace name should be atleast 6 characters long!!",
+ "team_name_too_short": "Workspace name should be atleast 6 characters long!!",
"user_not_found": "User not found in the infra!!"
},
"teams": {
diff --git a/packages/hoppscotch-sh-admin/src/components.d.ts b/packages/hoppscotch-sh-admin/src/components.d.ts
index 8deeb2b8..17e02a1c 100644
--- a/packages/hoppscotch-sh-admin/src/components.d.ts
+++ b/packages/hoppscotch-sh-admin/src/components.d.ts
@@ -1,54 +1,47 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
-import '@vue/runtime-core';
+import '@vue/runtime-core'
-export {};
+export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
- AppHeader: typeof import('./components/app/Header.vue')['default'];
- AppLogin: typeof import('./components/app/Login.vue')['default'];
- AppLogout: typeof import('./components/app/Logout.vue')['default'];
- AppModal: typeof import('./components/app/Modal.vue')['default'];
- AppSidebar: typeof import('./components/app/Sidebar.vue')['default'];
- AppToast: typeof import('./components/app/Toast.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'];
- HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete'];
- HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'];
- HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'];
- HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'];
- HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'];
- HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'];
- HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder'];
- HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper'];
- HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'];
- HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'];
- HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable'];
- HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'];
- HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'];
- IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'];
- IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default'];
- IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default'];
- IconLucideInbox: typeof import('~icons/lucide/inbox')['default'];
- IconLucideUser: typeof import('~icons/lucide/user')['default'];
- SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default'];
- SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default'];
- SettingsReset: typeof import('./components/settings/Reset.vue')['default'];
- SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default'];
- SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default'];
- TeamsAdd: typeof import('./components/teams/Add.vue')['default'];
- TeamsDetails: typeof import('./components/teams/Details.vue')['default'];
- TeamsInvite: typeof import('./components/teams/Invite.vue')['default'];
- TeamsMembers: typeof import('./components/teams/Members.vue')['default'];
- TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default'];
- Tippy: typeof import('vue-tippy')['Tippy'];
- UiAutoResetIcon: typeof import('./components/ui/AutoResetIcon.vue')['default'];
- UsersDetails: typeof import('./components/users/Details.vue')['default'];
- UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'];
- UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default'];
+ AppHeader: typeof import('./components/app/Header.vue')['default']
+ AppLogin: typeof import('./components/app/Login.vue')['default']
+ AppLogout: typeof import('./components/app/Logout.vue')['default']
+ AppModal: typeof import('./components/app/Modal.vue')['default']
+ AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
+ AppToast: typeof import('./components/app/Toast.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']
+ HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
+ HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
+ HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
+ HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
+ HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
+ HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
+ IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
+ SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
+ SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
+ SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
+ SettingsReset: typeof import('./components/settings/Reset.vue')['default']
+ SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default']
+ SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default']
+ SetupDataSharingAndNewsletter: typeof import('./components/setup/DataSharingAndNewsletter.vue')['default']
+ TeamsAdd: typeof import('./components/teams/Add.vue')['default']
+ TeamsDetails: typeof import('./components/teams/Details.vue')['default']
+ TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
+ TeamsMembers: typeof import('./components/teams/Members.vue')['default']
+ TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default']
+ Tippy: typeof import('vue-tippy')['Tippy']
+ UiAutoResetIcon: typeof import('./components/ui/AutoResetIcon.vue')['default']
+ UsersDetails: typeof import('./components/users/Details.vue')['default']
+ UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
+ UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default']
}
+
}
diff --git a/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue b/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue
index bd20a094..c1267fcb 100644
--- a/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue
+++ b/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue
@@ -2,6 +2,7 @@
+
diff --git a/packages/hoppscotch-sh-admin/src/components/settings/DataSharing.vue b/packages/hoppscotch-sh-admin/src/components/settings/DataSharing.vue
new file mode 100644
index 00000000..98367302
--- /dev/null
+++ b/packages/hoppscotch-sh-admin/src/components/settings/DataSharing.vue
@@ -0,0 +1,66 @@
+
+
+
+
{{ t('configs.data_sharing.title') }}
+
+ {{ t('configs.data_sharing.description') }}
+
+
+
+
+
+ {{ t('configs.data_sharing.title') }}
+
+
+
+
+ {{ t('configs.data_sharing.toggle_description') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/hoppscotch-sh-admin/src/components/settings/ServerRestart.vue b/packages/hoppscotch-sh-admin/src/components/settings/ServerRestart.vue
index 8d27964c..12dd3c62 100644
--- a/packages/hoppscotch-sh-admin/src/components/settings/ServerRestart.vue
+++ b/packages/hoppscotch-sh-admin/src/components/settings/ServerRestart.vue
@@ -22,6 +22,7 @@ import {
EnableAndDisableSsoDocument,
ResetInfraConfigsDocument,
UpdateInfraConfigsDocument,
+ ToggleAnalyticsCollectionDocument,
} from '~/helpers/backend/graphql';
const t = useI18n();
@@ -43,10 +44,17 @@ const updateInfraConfigsMutation = useMutation(UpdateInfraConfigsDocument);
const updateAllowedAuthProviderMutation = useMutation(
EnableAndDisableSsoDocument
);
+const toggleDataSharingMutation = useMutation(
+ ToggleAnalyticsCollectionDocument
+);
// Mutation handlers
-const { updateInfraConfigs, updateAuthProvider, resetInfraConfigs } =
- useConfigHandler(props.workingConfigs);
+const {
+ updateInfraConfigs,
+ updateAuthProvider,
+ resetInfraConfigs,
+ updateDataSharingConfigs,
+} = useConfigHandler(props.workingConfigs);
// Call relevant mutations on component mount and initiate server restart
const duration = ref(30);
@@ -80,6 +88,12 @@ onMounted(async () => {
updateAllowedAuthProviderMutation
);
if (!authResult) return;
+
+ const dataSharingResult = await updateDataSharingConfigs(
+ toggleDataSharingMutation
+ );
+
+ if (!dataSharingResult) return;
}
restart.value = true;
diff --git a/packages/hoppscotch-sh-admin/src/components/setup/DataSharingAndNewsletter.vue b/packages/hoppscotch-sh-admin/src/components/setup/DataSharingAndNewsletter.vue
new file mode 100644
index 00000000..443acf3f
--- /dev/null
+++ b/packages/hoppscotch-sh-admin/src/components/setup/DataSharingAndNewsletter.vue
@@ -0,0 +1,141 @@
+
+
+
+
{{ t('data_sharing.welcome') }}
+

+
+
+
+
+
+ {{ t('data_sharing.title') }}
+
+
+ {{ t('data_sharing.description') }}
+
+
+
+ {{ t('data_sharing.toggle_description') }}
+
+
+
+
+
+
+
+ {{ t('newsletter.title') }}
+
+
{{ t('newsletter.description') }}
+
+
+ {{ t('newsletter.toggle_description') }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/hoppscotch-sh-admin/src/composables/useClientHandler.ts b/packages/hoppscotch-sh-admin/src/composables/useClientHandler.ts
index 5a1ba0a1..4176e598 100644
--- a/packages/hoppscotch-sh-admin/src/composables/useClientHandler.ts
+++ b/packages/hoppscotch-sh-admin/src/composables/useClientHandler.ts
@@ -25,22 +25,26 @@ export function useClientHandler<
const fetchData = async () => {
fetching.value = true;
- try {
- const result = await client
- .query(query, {
- ...variables,
- })
- .toPromise();
- if (getList) {
- const resultList = getList(result.data!);
- dataAsList.value.push(...resultList);
- } else {
- data.value = result.data;
- }
- } catch (e) {
+ const result = await client
+ .query(query, {
+ ...variables,
+ })
+ .toPromise();
+
+ if (result.error) {
error.value = true;
+ fetching.value = false;
+ return;
}
+
+ if (getList) {
+ const resultList = getList(result.data!);
+ dataAsList.value.push(...resultList);
+ } else {
+ data.value = result.data;
+ }
+
fetching.value = false;
};
diff --git a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts
index 59aa6f7d..011ab369 100644
--- a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts
+++ b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts
@@ -1,19 +1,20 @@
-import { computed, onMounted, ref } from 'vue';
+import { AnyVariables, UseMutationResponse } from '@urql/vue';
import { cloneDeep } from 'lodash-es';
-import { UseMutationResponse } from '@urql/vue';
-import { useClientHandler } from './useClientHandler';
-import { useToast } from './toast';
+import { computed, onMounted, ref } from 'vue';
import { useI18n } from '~/composables/i18n';
import {
+ AllowedAuthProvidersDocument,
+ EnableAndDisableSsoArgs,
+ EnableAndDisableSsoMutation,
+ InfraConfigArgs,
InfraConfigEnum,
InfraConfigsDocument,
- AllowedAuthProvidersDocument,
- EnableAndDisableSsoMutation,
- UpdateInfraConfigsMutation,
ResetInfraConfigsMutation,
- EnableAndDisableSsoArgs,
- InfraConfigArgs,
+ ToggleAnalyticsCollectionMutation,
+ UpdateInfraConfigsMutation,
} from '~/helpers/backend/graphql';
+import { useToast } from './toast';
+import { useClientHandler } from './useClientHandler';
// Types
export type SsoAuthProviders = 'google' | 'microsoft' | 'github';
@@ -54,6 +55,11 @@ export type Config = {
mailer_from_address: string;
};
};
+
+ dataSharingConfigs: {
+ name: string;
+ enabled: boolean;
+ };
};
type UpdatedConfigs = {
@@ -86,6 +92,7 @@ export function useConfigHandler(updatedConfigs?: Config) {
'GITHUB_CLIENT_SECRET',
'MAILER_SMTP_URL',
'MAILER_ADDRESS_FROM',
+ 'ALLOW_ANALYTICS_COLLECTION',
] as InfraConfigEnum[],
},
(x) => x.infraConfigs
@@ -164,6 +171,12 @@ export function useConfigHandler(updatedConfigs?: Config) {
?.value ?? '',
},
},
+ dataSharingConfigs: {
+ name: 'data_sharing',
+ enabled: !!infraConfigs.value.find(
+ (x) => x.name === 'ALLOW_ANALYTICS_COLLECTION' && x.value === 'true'
+ ),
+ },
};
// Cloning the current configs to working configs
@@ -262,15 +275,23 @@ export function useConfigHandler(updatedConfigs?: Config) {
// Checking if any of the config fields are empty
const isFieldEmpty = (field: string) => field.trim() === '';
- const AreAnyConfigFieldsEmpty = (config: Config): boolean => {
- const providerFieldsEmpty = Object.values(config.providers).some(
- (provider) => Object.values(provider.fields).some(isFieldEmpty)
- );
- const mailFieldsEmpty = Object.values(config.mailConfigs.fields).some(
- isFieldEmpty
- );
+ type ConfigSection = {
+ enabled: boolean;
+ fields: Record;
+ };
- return providerFieldsEmpty || mailFieldsEmpty;
+ const AreAnyConfigFieldsEmpty = (config: Config): boolean => {
+ const sections: Array = [
+ config.providers.github,
+ config.providers.google,
+ config.providers.microsoft,
+ config.mailConfigs,
+ ];
+
+ return sections.some(
+ (section) =>
+ section.enabled && Object.values(section.fields).some(isFieldEmpty)
+ );
};
// Transforming the working configs back into the format required by the mutations
@@ -297,55 +318,70 @@ export function useConfigHandler(updatedConfigs?: Config) {
];
});
- // Updating the auth provider configurations
- const updateAuthProvider = async (
- updateProviderStatus: UseMutationResponse
- ) => {
- const variables = {
- providerInfo:
- updatedAllowedAuthProviders.value as EnableAndDisableSsoArgs[],
- };
-
- const result = await updateProviderStatus.executeMutation(variables);
+ // Generic function to handle mutation execution and error handling
+ const executeMutation = async (
+ mutation: UseMutationResponse,
+ variables: AnyVariables = undefined,
+ errorMessage: string
+ ): Promise => {
+ const result = await mutation.executeMutation(variables);
if (result.error) {
- toast.error(t('configs.auth_providers.update_failure'));
+ toast.error(t(errorMessage));
return false;
}
return true;
};
+ // Updating the auth provider configurations
+ const updateAuthProvider = (
+ updateProviderStatus: UseMutationResponse
+ ) =>
+ executeMutation(
+ updateProviderStatus,
+ {
+ providerInfo:
+ updatedAllowedAuthProviders.value as EnableAndDisableSsoArgs[],
+ },
+ 'configs.auth_providers.update_failure'
+ );
+
// Updating the infra configurations
- const updateInfraConfigs = async (
+ const updateInfraConfigs = (
updateInfraConfigsMutation: UseMutationResponse
- ) => {
- const variables = {
- infraConfigs: updatedInfraConfigs.value as InfraConfigArgs[],
- };
-
- const result = await updateInfraConfigsMutation.executeMutation(variables);
-
- if (result.error) {
- toast.error(t('configs.mail_configs.update_failure'));
- return false;
- }
-
- return true;
- };
+ ) =>
+ executeMutation(
+ updateInfraConfigsMutation,
+ {
+ infraConfigs: updatedInfraConfigs.value as InfraConfigArgs[],
+ },
+ 'configs.mail_configs.update_failure'
+ );
// Resetting the infra configurations
- const resetInfraConfigs = async (
+ const resetInfraConfigs = (
resetInfraConfigsMutation: UseMutationResponse
- ) => {
- const result = await resetInfraConfigsMutation.executeMutation();
+ ) =>
+ executeMutation(
+ resetInfraConfigsMutation,
+ undefined,
+ 'configs.reset.failure'
+ );
- if (result.error) {
- toast.error(t('configs.reset.failure'));
- return false;
- }
- return true;
- };
+ // Updating the data sharing configurations
+ const updateDataSharingConfigs = (
+ toggleDataSharingMutation: UseMutationResponse
+ ) =>
+ executeMutation(
+ toggleDataSharingMutation,
+ {
+ status: updatedConfigs?.dataSharingConfigs.enabled
+ ? 'ENABLE'
+ : 'DISABLE',
+ },
+ 'configs.data_sharing.update_failure'
+ );
return {
currentConfigs,
@@ -353,6 +389,7 @@ export function useConfigHandler(updatedConfigs?: Config) {
updatedInfraConfigs,
updatedAllowedAuthProviders,
updateAuthProvider,
+ updateDataSharingConfigs,
updateInfraConfigs,
resetInfraConfigs,
fetchingInfraConfigs,
diff --git a/packages/hoppscotch-sh-admin/src/composables/usePagedQuery.ts b/packages/hoppscotch-sh-admin/src/composables/usePagedQuery.ts
index 450749eb..67665529 100644
--- a/packages/hoppscotch-sh-admin/src/composables/usePagedQuery.ts
+++ b/packages/hoppscotch-sh-admin/src/composables/usePagedQuery.ts
@@ -1,4 +1,4 @@
-import { onMounted, ref } from 'vue';
+import { Ref, onMounted, ref } from 'vue';
import { DocumentNode } from 'graphql';
import { TypedDocumentNode, useClientHandle } from '@urql/vue';
@@ -16,38 +16,41 @@ export function usePagedQuery<
const { client } = useClientHandle();
const fetching = ref(true);
const error = ref(false);
- const list = ref([]);
+ const list: Ref = ref([]);
const currentPage = ref(0);
const hasNextPage = ref(true);
const fetchNextPage = async () => {
fetching.value = true;
- try {
- const cursor =
- list.value.length > 0 ? getCursor(list.value.at(-1)) : undefined;
- const variablesForPagination = {
- ...variables,
- take: itemsPerPage,
- cursor,
- };
+ const cursor =
+ list.value.length > 0 ? getCursor(list.value.at(-1)!) : undefined;
+ const variablesForPagination = {
+ ...variables,
+ take: itemsPerPage,
+ cursor,
+ };
- const result = await client
- .query(query, variablesForPagination)
- .toPromise();
- const resultList = getList(result.data!);
+ const result = await client
+ .query(query, variablesForPagination)
+ .toPromise();
- if (resultList.length < itemsPerPage) {
- hasNextPage.value = false;
- }
-
- list.value.push(...resultList);
- currentPage.value++;
- } catch (e) {
+ if (result.error) {
error.value = true;
- } finally {
fetching.value = false;
+ return;
}
+
+ const resultList = getList(result.data!);
+
+ if (resultList.length < itemsPerPage) {
+ hasNextPage.value = false;
+ }
+
+ list.value.push(...resultList);
+ currentPage.value++;
+
+ fetching.value = false;
};
onMounted(async () => {
diff --git a/packages/hoppscotch-sh-admin/src/helpers/auth.ts b/packages/hoppscotch-sh-admin/src/helpers/auth.ts
index 64251a49..b1ea2c69 100644
--- a/packages/hoppscotch-sh-admin/src/helpers/auth.ts
+++ b/packages/hoppscotch-sh-admin/src/helpers/auth.ts
@@ -67,7 +67,7 @@ const signOut = async (reloadWindow = false) => {
});
};
-const getInitialUserDetails = async () => {
+const getUserDetails = async () => {
const res = await authQuery.getUserDetails();
return res.data;
};
@@ -80,7 +80,7 @@ const setUser = (user: HoppUser | null) => {
const setInitialUser = async () => {
isGettingInitialUser.value = true;
- const res = await getInitialUserDetails();
+ const res = await getUserDetails();
if (res.errors?.[0]) {
const [error] = res.errors;
@@ -154,7 +154,7 @@ export const auth = {
getCurrentUserStream: () => currentUser$,
getAuthEventsStream: () => authEvents$,
getCurrentUser: () => currentUser$.value,
-
+ getUserDetails,
performAuthInit: () => {
const currentUser = JSON.parse(getLocalConfig('login_state') ?? 'null');
currentUser$.next(currentUser);
@@ -232,4 +232,24 @@ export const auth = {
const res = await authQuery.getProviders();
return res.data?.providers;
},
+
+ getFirstTimeInfraSetupStatus: async (): Promise => {
+ try {
+ const res = await authQuery.getFirstTimeInfraSetupStatus();
+ return res.data?.value === 'true';
+ } catch (err) {
+ // Setup is not done
+ return true;
+ }
+ },
+
+ updateFirstTimeInfraSetupStatus: async () => {
+ try {
+ await authQuery.updateFirstTimeInfraSetupStatus();
+ return true;
+ } catch (err) {
+ console.error(err);
+ return false;
+ }
+ },
};
diff --git a/packages/hoppscotch-sh-admin/src/helpers/axiosConfig.ts b/packages/hoppscotch-sh-admin/src/helpers/axiosConfig.ts
index 533ea35d..a250d8c7 100644
--- a/packages/hoppscotch-sh-admin/src/helpers/axiosConfig.ts
+++ b/packages/hoppscotch-sh-admin/src/helpers/axiosConfig.ts
@@ -17,4 +17,10 @@ const restApi = axios.create({
baseURL: import.meta.env.VITE_BACKEND_API_URL,
});
-export { gqlApi, restApi };
+const listmonkApi = axios.create({
+ ...baseConfig,
+ withCredentials: false,
+ baseURL: 'https://listmonk.hoppscotch.com/api/public',
+});
+
+export { gqlApi, restApi, listmonkApi };
diff --git a/packages/hoppscotch-sh-admin/src/helpers/backend/gql/mutations/ToggleAnalyticsCollection.graphql b/packages/hoppscotch-sh-admin/src/helpers/backend/gql/mutations/ToggleAnalyticsCollection.graphql
new file mode 100644
index 00000000..06966203
--- /dev/null
+++ b/packages/hoppscotch-sh-admin/src/helpers/backend/gql/mutations/ToggleAnalyticsCollection.graphql
@@ -0,0 +1,3 @@
+mutation ToggleAnalyticsCollection($status: ServiceStatus!) {
+ toggleAnalyticsCollection(status: $status)
+}
diff --git a/packages/hoppscotch-sh-admin/src/helpers/backend/rest/authQuery.ts b/packages/hoppscotch-sh-admin/src/helpers/backend/rest/authQuery.ts
index d6fc35a9..5ab3023b 100644
--- a/packages/hoppscotch-sh-admin/src/helpers/backend/rest/authQuery.ts
+++ b/packages/hoppscotch-sh-admin/src/helpers/backend/rest/authQuery.ts
@@ -29,5 +29,7 @@ export default {
token,
deviceIdentifier,
}),
+ getFirstTimeInfraSetupStatus: () => restApi.get('/site/setup'),
+ updateFirstTimeInfraSetupStatus: () => restApi.put('/site/setup'),
logout: () => restApi.get('/auth/logout'),
};
diff --git a/packages/hoppscotch-sh-admin/src/modules/admin.ts b/packages/hoppscotch-sh-admin/src/modules/admin.ts
index 03b50f4a..96c06dbd 100644
--- a/packages/hoppscotch-sh-admin/src/modules/admin.ts
+++ b/packages/hoppscotch-sh-admin/src/modules/admin.ts
@@ -1,15 +1,16 @@
import { auth } from '~/helpers/auth';
+import { UNAUTHORIZED } from '~/helpers/errors';
import { HoppModule } from '.';
-const isAdmin = () => {
- const user = auth.getCurrentUser();
- return user ? user.isAdmin : false;
+const isSetupRoute = (to: unknown) => to === 'setup';
+
+const isGuestRoute = (to: unknown) => ['index', 'enter'].includes(to as string);
+
+const getFirstTimeInfraSetupStatus = async () => {
+ const isInfraNotSetup = await auth.getFirstTimeInfraSetupStatus();
+ return isInfraNotSetup;
};
-const GUEST_ROUTES = ['index', 'enter'];
-
-const isGuestRoute = (to: unknown) => GUEST_ROUTES.includes(to as string);
-
/**
* @module routers
*/
@@ -24,13 +25,53 @@ const isGuestRoute = (to: unknown) => GUEST_ROUTES.includes(to as string);
*/
export default {
- onBeforeRouteChange(to, from, next) {
- if (!isGuestRoute(to.name) && !isAdmin()) {
- next({ name: 'index' });
- } else if (isGuestRoute(to.name) && isAdmin()) {
- next({ name: 'dashboard' });
- } else {
- next();
+ async onBeforeRouteChange(to, _from, next) {
+ const res = await auth.getUserDetails();
+
+ // Allow performing the silent refresh flow for an invalid access token state
+ if (res.errors?.[0].message === UNAUTHORIZED) {
+ return next();
}
+
+ const isAdmin = res.data?.me.isAdmin;
+
+ // Route Guards
+ if (!isGuestRoute(to.name) && !isAdmin) {
+ /**
+ * Reroutes the user to the login page if user is not logged in
+ * and is not an admin
+ */
+ return next({ name: 'index' });
+ }
+
+ if (isAdmin) {
+ // These route guards applies to the case where the user is logged in successfully and validated as an admin
+ const isInfraNotSetup = await getFirstTimeInfraSetupStatus();
+
+ /**
+ * Reroutes the user to the dashboard homepage if they have setup the infra already
+ * Else, the Setup page
+ */
+ if (isGuestRoute(to.name)) {
+ const name = isInfraNotSetup ? 'setup' : 'dashboard';
+ return next({ name });
+ }
+ /**
+ * Reroutes the user to the dashboard homepage if they have setup the infra already
+ * and are trying to access the setup page
+ */
+ if (isSetupRoute(to.name) && !isInfraNotSetup) {
+ return next({ name: 'dashboard' });
+ }
+ /**
+ * Reroutes the user to the setup page if they have not setup the infra yet
+ * and tries to access a valid route which is not a guest route
+ */
+ if (isInfraNotSetup && !isSetupRoute(to.name)) {
+ return next({ name: 'setup' });
+ }
+ }
+
+ next();
},
};
diff --git a/packages/hoppscotch-sh-admin/src/pages/setup.vue b/packages/hoppscotch-sh-admin/src/pages/setup.vue
new file mode 100644
index 00000000..64af3439
--- /dev/null
+++ b/packages/hoppscotch-sh-admin/src/pages/setup.vue
@@ -0,0 +1,40 @@
+
+ (isDataSharingAndNewsletterSetup = status)"
+ />
+
+
+
+
+
+meta:
+ layout: empty
+
diff --git a/packages/hoppscotch-sh-admin/src/pages/teams/_id.vue b/packages/hoppscotch-sh-admin/src/pages/teams/_id.vue
index 17d5b335..285d572a 100644
--- a/packages/hoppscotch-sh-admin/src/pages/teams/_id.vue
+++ b/packages/hoppscotch-sh-admin/src/pages/teams/_id.vue
@@ -4,7 +4,9 @@
- {{ t('teams.load_info_error') }}
+
+ {{ t('teams.load_info_error') }}
+
diff --git a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue
index 49d52576..11090138 100644
--- a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue
+++ b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue
@@ -1,6 +1,6 @@
- {{ t('users.load_info_error') }}
+ {{ t('users.load_info_error') }}