fix: SH admin dashboard setting and onboarding page cleanups (#5290)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
parent
56fa9534c7
commit
6597ade3f7
10 changed files with 367 additions and 281 deletions
|
|
@ -27,7 +27,11 @@
|
|||
@click="provider.action"
|
||||
/>
|
||||
|
||||
<hr v-if="additionalLoginItems.length > 0" />
|
||||
<hr
|
||||
v-if="
|
||||
additionalLoginItems.length > 0 && allowedAuthProviders.length
|
||||
"
|
||||
/>
|
||||
|
||||
<HoppSmartItem
|
||||
v-for="loginItem in additionalLoginItems"
|
||||
|
|
@ -58,7 +62,7 @@
|
|||
</form>
|
||||
|
||||
<div
|
||||
v-if="!allowedAuthProviders?.length"
|
||||
v-if="!allowedAuthProviders?.length && !additionalLoginItems.length"
|
||||
class="flex flex-col items-center text-center"
|
||||
>
|
||||
<p>{{ t("state.require_auth_provider") }}</p>
|
||||
|
|
|
|||
|
|
@ -176,7 +176,8 @@
|
|||
},
|
||||
"onboarding": {
|
||||
"add_atleast_one_auth_provider": "Please add at least one authentication provider to continue.",
|
||||
"add_configurations": "Please add the configurations for the selected authentication methods.",
|
||||
"add_configurations": "Add Configurations",
|
||||
"add_configurations_description": "Please add the configurations for the selected authentication methods.",
|
||||
"add_oauth_config": "Add Auth Configurations",
|
||||
"auth_setup": "Authentication Setup",
|
||||
"auth_successfully_configured": "authentication has been successfully configured.",
|
||||
|
|
@ -214,7 +215,8 @@
|
|||
"title": "SMTP"
|
||||
},
|
||||
"smtp_advanced_config_enable": "Enable to configure your own SMTP credentials",
|
||||
"select_auth_provider": "Please select the authentication methods you want to set up.",
|
||||
"select_auth_methods": "Please select authentications",
|
||||
"select_auth_provider": "You can select one or more authentication providers to continue.",
|
||||
"select_atleast_one": "Please select at least one authentication provider to continue.",
|
||||
"start_onboarding": "Start Onboarding",
|
||||
"welcome": "Welcome",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div
|
||||
class="flex flex-col justify-between space-y-2 p-5 w-56 h-46 cursor-pointer border-2 rounded-lg shadow transition bg-primaryLight border-primaryDark hover:border-accent"
|
||||
:class="{
|
||||
'!bg-primary !border-accentDark': selected,
|
||||
}"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<div class="flex flex-col space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[0.9rem] text-secondaryDark">
|
||||
{{ t(title) }}
|
||||
</span>
|
||||
<icon-lucide-check
|
||||
v-if="selected"
|
||||
class="svg-icons h-4 w-4 text-accent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="text-secondaryLight w-32">
|
||||
{{ t(description) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center -space-x-2">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import IconLucideCheck from '~icons/lucide/check';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
|
||||
defineProps<{
|
||||
title: string;
|
||||
description: string;
|
||||
selected: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['click']);
|
||||
|
||||
const t = useI18n();
|
||||
</script>
|
||||
|
|
@ -1,255 +1,185 @@
|
|||
<template>
|
||||
<div class="flex flex-col flex-1 py-8 p-4 justify-center w-full">
|
||||
<div class="w-full max-w-screen-md mx-auto">
|
||||
<div
|
||||
v-if="isFirstTimeSetup && authConfigStep === 1"
|
||||
class="py-8 rounded max-h-lg overflow-y-auto"
|
||||
>
|
||||
<div>
|
||||
<h1 class="text-[1rem] font-normal">
|
||||
{{ t('onboarding.select_auth_provider') }}
|
||||
</h1>
|
||||
<!-- auth providers -->
|
||||
<div class="space-y-2 flex flex-col">
|
||||
<div v-if="isFirstTimeSetup" class="flex max-w-xs space-x-3">
|
||||
<span class="w-10 h-1 rounded-sm bg-accent"></span>
|
||||
<span
|
||||
class="w-10 h-1 rounded-sm bg-primaryDark"
|
||||
:class="{
|
||||
'!bg-accent': authConfigStep === 2,
|
||||
}"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center space-x-4 my-8">
|
||||
<div
|
||||
class="flex flex-col space-y-2 p-5 h-40 cursor-pointer flex-1 border-2 bg-primaryLight rounded-lg border-primaryDark shadow hover:border-accent transition"
|
||||
:class="{
|
||||
'!bg-primary !border-accentDark':
|
||||
selectedOptions.includes('OAuth'),
|
||||
}"
|
||||
@click="toggleSelectedOption('OAuth')"
|
||||
>
|
||||
<span class="text-[0.9rem] text-secondaryDark">
|
||||
{{ t('onboarding.oauth.title') }}
|
||||
</span>
|
||||
<span class="text-secondaryLight h-10">
|
||||
{{ t('onboarding.oauth.description') }}
|
||||
</span>
|
||||
<div class="my-4">
|
||||
<div class="flex items-center -space-x-2">
|
||||
<img
|
||||
alt="user 1"
|
||||
src="/assets/icons/auth/google.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
<img
|
||||
alt="user 2"
|
||||
src="/assets/icons/auth/github.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
<img
|
||||
alt="user 3"
|
||||
src="/assets/icons/auth/microsoft.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col space-y-2 p-5 h-40 cursor-pointer flex-1 border-2 bg-primaryLight rounded-lg border-primaryDark shadow hover:border-accent transition"
|
||||
:class="{
|
||||
'!bg-primary !border-accentDark':
|
||||
selectedOptions.includes('SMTP'),
|
||||
}"
|
||||
@click="toggleSelectedOption('SMTP')"
|
||||
>
|
||||
<span class="text-[0.9rem] text-secondaryDark">
|
||||
{{ t('onboarding.smtp.title') }}
|
||||
</span>
|
||||
<span class="text-secondaryLight h-10">
|
||||
{{ t('onboarding.smtp.description') }}
|
||||
</span>
|
||||
<div class="my-4">
|
||||
<div class="flex items-center -space-x-2">
|
||||
<img
|
||||
alt="user 2"
|
||||
src="/assets/icons/auth/email.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HoppButtonPrimary
|
||||
:label="t('onboarding.add_oauth_config')"
|
||||
@click="addAuthConfig"
|
||||
:icon="IconLucideArrowRight"
|
||||
:reverse="true"
|
||||
class="mt-6"
|
||||
/>
|
||||
<div class="flex flex-col space-y-1 py-6">
|
||||
<template v-if="authConfigStep === 1">
|
||||
<h1 class="text-xl">{{ t('onboarding.select_auth_methods') }}</h1>
|
||||
<h2 class="text-base font-normal text-secondaryLight">
|
||||
{{ t('onboarding.select_auth_provider') }}
|
||||
</h2>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 class="text-xl">{{ t('onboarding.add_configurations') }}</h1>
|
||||
<h2 class="text-base font-normal text-secondaryLight">
|
||||
{{ t('onboarding.add_configurations_description') }}
|
||||
</h2>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auth setup step 2 -->
|
||||
<div v-else-if="authConfigStep === 2">
|
||||
<button
|
||||
v-if="isFirstTimeSetup"
|
||||
class="items-center flex space-x-2 cursor-pointer mb-4 group"
|
||||
@click="authConfigStep = 1"
|
||||
>
|
||||
<span class="group-hover:opacity-80 transition-opacity">
|
||||
<IconLucideArrowLeft class="svg-icons" />
|
||||
</span>
|
||||
<span class="group-hover:opacity-80 transition-opacity">
|
||||
{{ t('app.back') }}
|
||||
</span>
|
||||
</button>
|
||||
<h2>
|
||||
{{ t('onboarding.add_configurations') }}
|
||||
</h2>
|
||||
<div class="min-h-[50vh]">
|
||||
<template v-if="isFirstTimeSetup && authConfigStep === 1">
|
||||
<div class="rounded max-h-lg">
|
||||
<div class="flex items-center gap-4 mb-8 flex-wrap">
|
||||
<AuthProviderCard
|
||||
title="onboarding.oauth.title"
|
||||
description="onboarding.oauth.description"
|
||||
:selected="isSelected('OAUTH') && isOAuthEnabled"
|
||||
@click="toggleSelectedOption('OAUTH')"
|
||||
>
|
||||
<img
|
||||
alt="user 1"
|
||||
src="/assets/icons/auth/google.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
<img
|
||||
alt="user 2"
|
||||
src="/assets/icons/auth/github.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
<img
|
||||
alt="user 3"
|
||||
src="/assets/icons/auth/microsoft.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
</AuthProviderCard>
|
||||
|
||||
<div class="my-5 overflow-y-auto max-h-[60vh]">
|
||||
<div
|
||||
id="accordion-nested-parent"
|
||||
data-accordion="collapse"
|
||||
class="space-y-4"
|
||||
>
|
||||
<AuthProviderCard
|
||||
title="onboarding.smtp.title"
|
||||
description="onboarding.smtp.description"
|
||||
:selected="isSelected('SMTP')"
|
||||
@click="toggleSelectedOption('SMTP')"
|
||||
>
|
||||
<img
|
||||
alt="user 2"
|
||||
src="/assets/icons/auth/email.svg"
|
||||
class="relative inline-block h-6 w-6 rounded-full border-2 border-primary object-cover object-center hover:z-10 focus:z-10"
|
||||
/>
|
||||
</AuthProviderCard>
|
||||
</div>
|
||||
|
||||
<HoppButtonPrimary
|
||||
:label="t('onboarding.add_oauth_config')"
|
||||
@click="proceedToConfig"
|
||||
:icon="IconLucideArrowRight"
|
||||
:reverse="true"
|
||||
class="mt-6"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- configure selected providers -->
|
||||
<template v-else-if="authConfigStep === 2">
|
||||
<div class="my-5 overflow-y-auto max-h-[60vh] space-y-4">
|
||||
<UiAccordion
|
||||
v-if="selectedOptions.includes('OAuth')"
|
||||
:initial-open="isOAuthEnabled"
|
||||
v-if="isSelected('OAUTH')"
|
||||
:initial-open="isOAuthEnabled || isSelected('OAUTH')"
|
||||
title="onboarding.oauth.title"
|
||||
description="onboarding.oauth.description_accordian"
|
||||
@toggle="toggleConfig('OAUTH')"
|
||||
class="bg-primary rounded-lg border-primaryDark shadow p-4 border flex flex-col"
|
||||
>
|
||||
<template v-slot:header="{ isOpen, toggleAccordion }">
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between flex-1 mb-2">
|
||||
<span class="font-semibold text-[0.8rem]"
|
||||
>{{ t('onboarding.oauth.title') }}
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<HoppSmartToggle
|
||||
:on="isOpen"
|
||||
@change="
|
||||
() => {
|
||||
toggleAccordion();
|
||||
toggleConfig('OAUTH');
|
||||
}
|
||||
"
|
||||
class="ml-2"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-tiny text-secondaryLight">
|
||||
{{ t('onboarding.oauth.description_accordian') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<OAuthSetup
|
||||
v-model:currentConfigs="currentConfigs"
|
||||
v-model:enabledConfigs="enabledConfigs"
|
||||
@toggleConfig="toggleConfig"
|
||||
/>
|
||||
</template>
|
||||
<OAuthSetup
|
||||
v-model:currentConfigs="currentConfigs"
|
||||
v-model:enabledConfigs="enabledConfigs"
|
||||
@toggleConfig="toggleConfig"
|
||||
/>
|
||||
</UiAccordion>
|
||||
|
||||
<UiAccordion
|
||||
v-if="selectedOptions.includes('SMTP')"
|
||||
:initial-open="enabledConfigs.includes('MAILER')"
|
||||
v-if="isSelected('SMTP')"
|
||||
:initial-open="isSmtpEnabled"
|
||||
title="onboarding.smtp.title"
|
||||
description="onboarding.smtp.description_accordian"
|
||||
@toggle="
|
||||
() => {
|
||||
toggleConfig('EMAIL');
|
||||
toggleSmtpConfig();
|
||||
}
|
||||
"
|
||||
class="bg-primary rounded-lg border-primaryDark shadow p-4 border flex flex-col"
|
||||
>
|
||||
<template v-slot:header="{ isOpen, toggleAccordion }">
|
||||
<div class="w-full">
|
||||
<div class="flex items-center justify-between flex-1 mb-2">
|
||||
<span class="font-semibold text-[0.8rem]">
|
||||
{{ t('onboarding.smtp.title') }}
|
||||
</span>
|
||||
<span>
|
||||
<HoppSmartToggle
|
||||
:on="isOpen"
|
||||
@change="
|
||||
() => {
|
||||
toggleAccordion();
|
||||
toggleConfig('EMAIL');
|
||||
toggleSmtpConfig();
|
||||
}
|
||||
"
|
||||
class="ml-2"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-secondaryLight text-tiny">
|
||||
{{ t('onboarding.smtp.description_accordian') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<SmtpSetup
|
||||
v-model:currentConfigs="currentConfigs"
|
||||
v-model:enabledConfigs="enabledConfigs"
|
||||
/>
|
||||
</template>
|
||||
<SmtpSetup
|
||||
v-model:currentConfigs="currentConfigs"
|
||||
v-model:enabledConfigs="enabledConfigs"
|
||||
/>
|
||||
</UiAccordion>
|
||||
</div>
|
||||
</div>
|
||||
<HoppButtonPrimary
|
||||
:label="t('onboarding.save_auth_config')"
|
||||
@click="addOnboardingConfigs"
|
||||
:reverse="true"
|
||||
:icon="IconLucideSave"
|
||||
:loading="submittingConfigs"
|
||||
class="mt-4"
|
||||
/>
|
||||
|
||||
<div class="flex items-center space-x-4 mt-6">
|
||||
<HoppButtonSecondary
|
||||
v-if="isFirstTimeSetup && authConfigStep === 2"
|
||||
:label="t('app.back')"
|
||||
@click="
|
||||
() => {
|
||||
authConfigStep = 1;
|
||||
selectedOptions = [];
|
||||
enabledConfigs = [];
|
||||
}
|
||||
"
|
||||
:reverse="true"
|
||||
:icon="IconLucideArrowLeft"
|
||||
:loading="submittingConfigs"
|
||||
:outline="true"
|
||||
/>
|
||||
<HoppButtonPrimary
|
||||
:label="t('onboarding.save_auth_config')"
|
||||
@click="submitConfigs"
|
||||
:reverse="true"
|
||||
:icon="IconLucideSave"
|
||||
:loading="submittingConfigs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { useToast } from '~/composables/toast';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import {
|
||||
useOnboardingConfigHandler,
|
||||
OAuthProvider,
|
||||
OnBoardingSummary,
|
||||
useOnboardingConfigHandler,
|
||||
} from '~/composables/useOnboardingConfigHandler';
|
||||
|
||||
import OAuthSetup from './OAuthSetup.vue';
|
||||
import SmtpSetup from './SmtpSetup.vue';
|
||||
|
||||
import IconLucideArrowRight from '~icons/lucide/arrow-right';
|
||||
import IconLucideArrowLeft from '~icons/lucide/arrow-left';
|
||||
import IconLucideSave from '~icons/lucide/save';
|
||||
import { useToast } from '~/composables/toast';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
import AuthProviderCard from './AuthProviderCard.vue';
|
||||
|
||||
const t = useI18n();
|
||||
const toast = useToast();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
isFirstTimeSetup: boolean;
|
||||
}>(),
|
||||
{
|
||||
isFirstTimeSetup: true,
|
||||
}
|
||||
);
|
||||
|
||||
const props = defineProps<{ isFirstTimeSetup?: boolean }>();
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'complete-onboarding',
|
||||
payload: {
|
||||
submittingConfigs: boolean;
|
||||
summary: OnBoardingSummary;
|
||||
}
|
||||
payload: { submittingConfigs: boolean; summary: OnBoardingSummary }
|
||||
): void;
|
||||
}>();
|
||||
|
||||
type SelectedOption = 'OAUTH' | 'SMTP';
|
||||
|
||||
const authConfigStep = ref(1);
|
||||
|
||||
const selectedOptions = ref<Array<'OAuth' | 'SMTP' | ''>>([]);
|
||||
|
||||
watch(
|
||||
() => props.isFirstTimeSetup,
|
||||
(newValue) => {
|
||||
if (!newValue) {
|
||||
authConfigStep.value = 2; // Skip to step 2 if not first time setup
|
||||
selectedOptions.value = ['OAuth', 'SMTP']; // Default to both options
|
||||
} else {
|
||||
authConfigStep.value = 1; // Reset to step 1 for first time setup
|
||||
selectedOptions.value = []; // Reset selected options
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
const selectedOptions = ref<SelectedOption[]>([]);
|
||||
|
||||
const {
|
||||
currentConfigs,
|
||||
|
|
@ -262,54 +192,88 @@ const {
|
|||
toggleSmtpConfig,
|
||||
} = useOnboardingConfigHandler();
|
||||
|
||||
const OAuthProviders: OAuthProvider[] = ['GOOGLE', 'GITHUB', 'MICROSOFT'];
|
||||
|
||||
const OAuthProviders: (OAuthProvider | 'OAUTH')[] = [
|
||||
'GOOGLE',
|
||||
'GITHUB',
|
||||
'MICROSOFT',
|
||||
'OAUTH',
|
||||
];
|
||||
const isOAuthEnabled = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
// Check if any OAuth provider is enabled
|
||||
isOAuthEnabled.value = OAuthProviders.some((provider) =>
|
||||
enabledConfigs.value.includes(provider)
|
||||
);
|
||||
updateOAuthEnabled();
|
||||
});
|
||||
|
||||
watch(isProvidersLoading, (loading) => {
|
||||
if (!loading) updateOAuthEnabled();
|
||||
});
|
||||
|
||||
watch(
|
||||
isProvidersLoading,
|
||||
(isLoading) => {
|
||||
if (!isLoading) {
|
||||
isOAuthEnabled.value = OAuthProviders.some((provider) =>
|
||||
enabledConfigs.value.includes(provider)
|
||||
);
|
||||
() => enabledConfigs.value,
|
||||
(configs) => {
|
||||
isOAuthEnabled.value = OAuthProviders.some((provider) =>
|
||||
configs.includes(provider)
|
||||
);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.isFirstTimeSetup,
|
||||
(initialSetup) => {
|
||||
if (initialSetup) {
|
||||
authConfigStep.value = 1;
|
||||
selectedOptions.value = [];
|
||||
} else {
|
||||
authConfigStep.value = 2;
|
||||
selectedOptions.value = ['OAUTH', 'SMTP'];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const addOnboardingConfigs = async () => {
|
||||
const isSelected = (option: SelectedOption) =>
|
||||
selectedOptions.value.includes(option as any);
|
||||
|
||||
const isSmtpEnabled = computed(() => enabledConfigs.value.includes('MAILER'));
|
||||
|
||||
const updateOAuthEnabled = () => {
|
||||
isOAuthEnabled.value = OAuthProviders.some((provider) =>
|
||||
enabledConfigs.value.includes(provider)
|
||||
);
|
||||
};
|
||||
|
||||
const toggleSelectedOption = (option: SelectedOption) => {
|
||||
if (selectedOptions.value.includes(option as any)) {
|
||||
selectedOptions.value = selectedOptions.value.filter(
|
||||
(opt) => opt !== option
|
||||
);
|
||||
} else {
|
||||
selectedOptions.value.push(option as any);
|
||||
}
|
||||
if (option === 'SMTP') {
|
||||
toggleConfig('EMAIL');
|
||||
toggleSmtpConfig();
|
||||
} else {
|
||||
toggleConfig(option);
|
||||
}
|
||||
};
|
||||
|
||||
const proceedToConfig = () => {
|
||||
if (!selectedOptions.value.length) {
|
||||
toast.error(t('onboarding.select_atleast_one'));
|
||||
return;
|
||||
}
|
||||
authConfigStep.value = 2;
|
||||
};
|
||||
|
||||
const submitConfigs = async () => {
|
||||
const res = await addOnBoardingConfigs();
|
||||
if (res && res.token) {
|
||||
if (res?.token) {
|
||||
emit('complete-onboarding', {
|
||||
submittingConfigs: submittingConfigs.value,
|
||||
summary: onBoardingSummary.value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const toggleSelectedOption = (option: 'OAuth' | 'SMTP') => {
|
||||
if (selectedOptions.value.includes(option)) {
|
||||
selectedOptions.value = selectedOptions.value.filter(
|
||||
(opt) => opt !== option
|
||||
);
|
||||
} else {
|
||||
selectedOptions.value.push(option);
|
||||
}
|
||||
};
|
||||
|
||||
const addAuthConfig = () => {
|
||||
if (selectedOptions.value.length === 0) {
|
||||
toast.error(t('onboarding.select_atleast_one'));
|
||||
return;
|
||||
}
|
||||
authConfigStep.value = 2;
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
<icon-lucide-badge-check class="svg-icons text-green-500 !h-8 !w-8" />
|
||||
</span>
|
||||
</h1>
|
||||
<h2>
|
||||
<h2 class="text-base">
|
||||
{{ t('onboarding.setup_complete.description') }}
|
||||
<br />
|
||||
<span class="text-secondaryLight text-tiny">
|
||||
<span class="text-secondaryLight text-[0.9rem]">
|
||||
{{ t('onboarding.setup_complete.description_sub') }}
|
||||
</span>
|
||||
</h2>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<HoppSmartInput
|
||||
v-model="currentConfigs.mailerConfigs[smtp.ADDRESS_FROM.id]"
|
||||
:label="smtp.ADDRESS_FROM.text"
|
||||
:autofocus="false"
|
||||
input-styles="floating-input"
|
||||
class="!my-2 !bg-primaryLight flex-1"
|
||||
/>
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
v-if="smtp.SMTP_URL.enabled"
|
||||
v-model="currentConfigs.mailerConfigs[smtp.SMTP_URL.id]"
|
||||
:label="smtp.SMTP_URL.text"
|
||||
:autofocus="false"
|
||||
input-styles="floating-input"
|
||||
class="!my-2 !bg-primaryLight flex-1"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@
|
|||
placeholder="e.g., 60 (in seconds)"
|
||||
:autofocus="false"
|
||||
class="!my-2 !bg-primaryLight flex-1"
|
||||
type="number"
|
||||
@update:model-value="
|
||||
validateNumberValue(rateLimitConfig.fields.rate_limit_ttl)
|
||||
"
|
||||
|
|
@ -46,7 +45,6 @@
|
|||
placeholder="e.g., 100 (requests per TTL)"
|
||||
:autofocus="false"
|
||||
class="!my-2 !bg-primaryLight flex-1"
|
||||
type="number"
|
||||
@update:model-value="
|
||||
validateNumberValue(rateLimitConfig.fields.rate_limit_max)
|
||||
"
|
||||
|
|
|
|||
|
|
@ -1,43 +1,77 @@
|
|||
<template>
|
||||
<div>
|
||||
<section class="w-full">
|
||||
<div
|
||||
class="flex items-center space-x-3"
|
||||
class="flex items-center justify-between w-full space-x-3"
|
||||
:aria-expanded="isOpen"
|
||||
:aria-controls="UID"
|
||||
:aria-controls="contentId"
|
||||
>
|
||||
<slot
|
||||
name="header"
|
||||
:is-open="isOpen"
|
||||
:toggle-accordion="toggleAccordion"
|
||||
/>
|
||||
<slot name="header" :is-open="isOpen" :toggle-accordion="toggleAccordion">
|
||||
<div class="flex flex-col text-left w-full">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="font-semibold text-[0.8rem]">
|
||||
{{ t(title || '') }}
|
||||
</span>
|
||||
<HoppSmartToggle
|
||||
:on="isOpen"
|
||||
@change="toggleAccordion"
|
||||
class="ml-2"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-tiny text-secondaryLight">
|
||||
{{ t(description || '') }}
|
||||
</span>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<div v-show="isOpen" :aria-labelledby="UID" :id="UID">
|
||||
<slot name="content" />
|
||||
<div
|
||||
v-show="isOpen"
|
||||
:id="contentId"
|
||||
role="region"
|
||||
:aria-labelledby="headerId"
|
||||
class="transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<slot name="content">
|
||||
<div class="mt-2">
|
||||
<slot />
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { useI18n } from '~/composables/i18n';
|
||||
|
||||
const UID = 'accordion-' + Math.random().toString(36).substr(2, 9);
|
||||
const t = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
initialOpen?: boolean;
|
||||
title?: string; // Optional fallback title
|
||||
description?: string; // Optional fallback description
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'toggle', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const idSuffix = Math.random().toString(36).substring(2, 9);
|
||||
const contentId = `accordion-content-${idSuffix}`;
|
||||
const headerId = `accordion-header-${idSuffix}`;
|
||||
|
||||
const isOpen = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.initialOpen,
|
||||
(newVal) => {
|
||||
isOpen.value = newVal ?? false;
|
||||
(val) => {
|
||||
isOpen.value = val ?? false;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const toggleAccordion = () => {
|
||||
isOpen.value = !isOpen.value;
|
||||
emit('toggle', isOpen.value);
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -197,6 +197,32 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
return field.trim() === '';
|
||||
};
|
||||
|
||||
/**
|
||||
* This is used to validate number fields, ensuring they are not NaN or less than or equal to zero.
|
||||
* It checks if the field is a number or a numeric string, and returns true if it is not valid.
|
||||
* @param field Field value to validate
|
||||
* @returns Boolean indicating if the field is not valid
|
||||
*/
|
||||
const isNotValidNumber = (field: string | boolean | number) => {
|
||||
if (typeof field === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Accept numbers or numeric strings (e.g., "1000"), but not non-numeric strings (e.g., "abc")
|
||||
if (typeof field === 'number') {
|
||||
return isNaN(field);
|
||||
}
|
||||
|
||||
if (typeof field === 'string') {
|
||||
// Trim and check if the string is a valid number
|
||||
const trimmed = field.trim();
|
||||
if (trimmed === '') return true;
|
||||
return isNaN(Number(trimmed));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the field is not valid
|
||||
* This is used to validate number fields, ensuring they are not NaN or less than or equal to zero.
|
||||
|
|
@ -227,27 +253,35 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
|
|||
];
|
||||
|
||||
const hasSectionWithEmptyFields = sections.some((section) => {
|
||||
if (
|
||||
section.name === 'email' &&
|
||||
!section.fields.mailer_use_custom_configs
|
||||
) {
|
||||
if (section.name === 'email') {
|
||||
const { mailer_use_custom_configs, ...otherFields } = section.fields;
|
||||
|
||||
const excludeKeys = mailer_use_custom_configs
|
||||
? ['mailer_smtp_url']
|
||||
: [
|
||||
'mailer_smtp_host',
|
||||
'mailer_smtp_port',
|
||||
'mailer_smtp_user',
|
||||
'mailer_smtp_password',
|
||||
];
|
||||
|
||||
return (
|
||||
section.enabled &&
|
||||
Object.entries(section.fields).some(
|
||||
([key, value]) =>
|
||||
isFieldEmpty(value) &&
|
||||
key !== 'mailer_smtp_host' &&
|
||||
key !== 'mailer_smtp_port' &&
|
||||
key !== 'mailer_smtp_user' &&
|
||||
key !== 'mailer_smtp_password'
|
||||
Object.entries(otherFields).some(
|
||||
([key, value]) => isFieldEmpty(value) && !excludeKeys.includes(key)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// This section has no enabled property, so we check fields directly
|
||||
// eg: tokenConfigs, rateLimitConfigs
|
||||
if (!('enabled' in section))
|
||||
Object.values(section.fields).some(isFieldNotValid);
|
||||
// for a valid number (>0) or non-empty string
|
||||
if (section.name === 'token')
|
||||
return Object.values(section.fields).some(isFieldNotValid);
|
||||
|
||||
// For rate limit section, we want to check if the values are not valid numbers
|
||||
// and not empty strings
|
||||
if (section.name === 'rate_limit')
|
||||
return Object.values(section.fields).some(isNotValidNumber);
|
||||
|
||||
return (
|
||||
section.enabled && Object.values(section.fields).some(isFieldEmpty)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { getLocalConfig, setLocalConfig } from '~/helpers/localpersistence';
|
|||
import { makeReadableKey } from '~/helpers/utils/readableKey';
|
||||
|
||||
export type OAuthProvider = 'GOOGLE' | 'GITHUB' | 'MICROSOFT';
|
||||
export type EnabledConfig = OAuthProvider | 'MAILER' | 'EMAIL';
|
||||
export type EnabledConfig = OAuthProvider | 'OAUTH' | 'MAILER' | 'EMAIL';
|
||||
|
||||
// common OAuth keys used across providers
|
||||
type OAuthKeys = 'CLIENT_ID' | 'CLIENT_SECRET' | 'CALLBACK_URL' | 'SCOPE';
|
||||
|
|
@ -132,7 +132,6 @@ export function useOnboardingConfigHandler() {
|
|||
enabledConfigs.value = enabledConfigs.value.filter(
|
||||
(c) => !['GOOGLE', 'GITHUB', 'MICROSOFT'].includes(c)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === 'EMAIL') {
|
||||
|
|
@ -292,11 +291,14 @@ export function useOnboardingConfigHandler() {
|
|||
return;
|
||||
}
|
||||
|
||||
const filteredEnabledConfigs = enabledConfigs.value.filter(
|
||||
(config) => config !== 'OAUTH' && config !== 'MAILER'
|
||||
);
|
||||
|
||||
const configWithAuth = {
|
||||
...validated,
|
||||
[InfraConfigEnum.ViteAllowedAuthProviders]: enabledConfigs.value
|
||||
.filter((x) => x !== 'MAILER')
|
||||
.join(','),
|
||||
[InfraConfigEnum.ViteAllowedAuthProviders]:
|
||||
filteredEnabledConfigs.join(','),
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in a new issue