refactor(editor): Fix TypeScript issues in views (no-changelog) (#9573)

This commit is contained in:
Elias Meire 2024-05-31 15:52:00 +02:00 committed by GitHub
parent 327794127e
commit e23420d89d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 89 additions and 70 deletions

View file

@ -250,7 +250,7 @@ export interface IWorkflowData {
export interface IWorkflowDataUpdate { export interface IWorkflowDataUpdate {
id?: string; id?: string;
name?: string; name?: string;
nodes?: INode[]; nodes?: Array<INode | IWorkflowTemplateNode>;
connections?: IConnections; connections?: IConnections;
settings?: IWorkflowSettings; settings?: IWorkflowSettings;
active?: boolean; active?: boolean;
@ -363,6 +363,7 @@ export interface ICredentialsResponse extends ICredentialsEncrypted {
homeProject?: ProjectSharingData; homeProject?: ProjectSharingData;
currentUserHasAccess?: boolean; currentUserHasAccess?: boolean;
scopes?: Scope[]; scopes?: Scope[];
ownedBy?: Pick<IUserResponse, 'id' | 'firstName' | 'lastName' | 'email'>;
} }
export interface ICredentialsBase { export interface ICredentialsBase {

View file

@ -64,7 +64,7 @@ export default function useCanvasMouseSelect() {
selectActive.value = false; selectActive.value = false;
} }
function _getSelectionBox(event: MouseEvent) { function _getSelectionBox(event: MouseEvent | TouchEvent) {
const [x, y] = getMousePositionWithinNodeView(event); const [x, y] = getMousePositionWithinNodeView(event);
return { return {
x: Math.min(x, selectBox.value.x), x: Math.min(x, selectBox.value.x),
@ -74,7 +74,7 @@ export default function useCanvasMouseSelect() {
}; };
} }
function _getNodesInSelection(event: MouseEvent): INodeUi[] { function _getNodesInSelection(event: MouseEvent | TouchEvent): INodeUi[] {
const returnNodes: INodeUi[] = []; const returnNodes: INodeUi[] = [];
const selectionBox = _getSelectionBox(event); const selectionBox = _getSelectionBox(event);
@ -128,9 +128,9 @@ export default function useCanvasMouseSelect() {
_updateSelectBox(e); _updateSelectBox(e);
} }
function mouseUpMouseSelect(e: MouseEvent) { function mouseUpMouseSelect(e: MouseEvent | TouchEvent) {
// Ignore right-click // Ignore right-click
if (e.button === 2 || isContextMenuOpen.value) return; if (('button' in e && e.button === 2) || isContextMenuOpen.value) return;
if (!selectActive.value) { if (!selectActive.value) {
if (isTouchDevice && e.target instanceof HTMLElement) { if (isTouchDevice && e.target instanceof HTMLElement) {

View file

@ -212,7 +212,7 @@ export const useUIStore = defineStore(STORES.UI, {
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const deploymentType = settingsStore.deploymentType; const deploymentType = settingsStore.deploymentType;
let contextKey = ''; let contextKey: '' | '.cloud' | '.desktop' = '';
if (deploymentType === 'cloud') { if (deploymentType === 'cloud') {
contextKey = '.cloud'; contextKey = '.cloud';
} else if (deploymentType === 'desktop_mac' || deploymentType === 'desktop_win') { } else if (deploymentType === 'desktop_mac' || deploymentType === 'desktop_win') {
@ -266,7 +266,7 @@ export const useUIStore = defineStore(STORES.UI, {
}, },
}, },
}, },
}; } as const;
}, },
getLastSelectedNode(): INodeUi | null { getLastSelectedNode(): INodeUi | null {
const workflowsStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();

View file

@ -16,6 +16,7 @@ import { NodeConnectionType } from 'n8n-workflow';
import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'; import type { BrowserJsPlumbInstance } from '@jsplumb/browser-ui';
import { EVENT_CONNECTION_MOUSEOUT, EVENT_CONNECTION_MOUSEOVER } from '@jsplumb/browser-ui'; import { EVENT_CONNECTION_MOUSEOUT, EVENT_CONNECTION_MOUSEOVER } from '@jsplumb/browser-ui';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import type { StyleValue } from 'vue';
/* /*
Canvas constants and functions. Canvas constants and functions.
@ -609,7 +610,7 @@ export const getBackgroundStyles = (
scale: number, scale: number,
offsetPosition: XYPosition, offsetPosition: XYPosition,
executionPreview: boolean, executionPreview: boolean,
) => { ): StyleValue => {
const squareSize = GRID_SIZE * scale; const squareSize = GRID_SIZE * scale;
const dotSize = 1 * scale; const dotSize = 1 * scale;
const dotPosition = (GRID_SIZE / 2) * scale; const dotPosition = (GRID_SIZE / 2) * scale;
@ -623,7 +624,7 @@ export const getBackgroundStyles = (
}; };
} }
const styles: object = { const styles: StyleValue = {
'background-size': `${squareSize}px ${squareSize}px`, 'background-size': `${squareSize}px ${squareSize}px`,
'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`, 'background-position': `left ${offsetPosition[0]}px top ${offsetPosition[1]}px`,
}; };

View file

@ -22,10 +22,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { type PropType, defineComponent } from 'vue';
import Logo from '@/components/Logo.vue'; import Logo from '@/components/Logo.vue';
import SSOLogin from '@/components/SSOLogin.vue'; import SSOLogin from '@/components/SSOLogin.vue';
import type { IFormBoxConfig } from '@/Interface';
export default defineComponent({ export default defineComponent({
name: 'AuthView', name: 'AuthView',
@ -34,7 +35,9 @@ export default defineComponent({
SSOLogin, SSOLogin,
}, },
props: { props: {
form: {}, form: {
type: Object as PropType<IFormBoxConfig>,
},
formLoading: { formLoading: {
type: Boolean, type: Boolean,
default: false, default: false,

View file

@ -18,20 +18,22 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import type { BaseTextKey } from '@/plugins/i18n';
import { type PropType, defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'ErrorView', name: 'ErrorView',
props: { props: {
messageKey: { messageKey: {
type: String, type: String as PropType<BaseTextKey>,
required: true, required: true,
}, },
errorCode: { errorCode: {
type: Number, type: Number,
}, },
redirectTextKey: { redirectTextKey: {
type: String, type: String as PropType<BaseTextKey>,
required: true,
}, },
redirectPage: { redirectPage: {
type: String, type: String,

View file

@ -4,7 +4,7 @@
<n8n-heading size="2xlarge"> <n8n-heading size="2xlarge">
{{ $locale.baseText('settings.api') }} {{ $locale.baseText('settings.api') }}
<span :style="{ fontSize: 'var(--font-size-s)', color: 'var(--color-text-light)' }"> <span :style="{ fontSize: 'var(--font-size-s)', color: 'var(--color-text-light)' }">
({{ $locale.baseText('beta') }}) ({{ $locale.baseText('generic.beta') }})
</span> </span>
</n8n-heading> </n8n-heading>
</div> </div>

View file

@ -4,7 +4,7 @@
<n8n-heading size="2xlarge">{{ <n8n-heading size="2xlarge">{{
i18n.baseText('settings.personal.personalSettings') i18n.baseText('settings.personal.personalSettings')
}}</n8n-heading> }}</n8n-heading>
<div :class="$style.user"> <div v-if="currentUser" :class="$style.user">
<span :class="$style.username" data-test-id="current-user-name"> <span :class="$style.username" data-test-id="current-user-name">
<n8n-text color="text-light">{{ currentUser.fullName }}</n8n-text> <n8n-text color="text-light">{{ currentUser.fullName }}</n8n-text>
</span> </span>

View file

@ -219,7 +219,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="repoUrlValidationRules" :validation-rules="repoUrlValidationRules"
:disabled="isConnected" :disabled="isConnected"
:placeholder="locale.baseText('settings.sourceControl.repoUrlPlaceholder')" :placeholder="locale.baseText('settings.sourceControl.repoUrlPlaceholder')"
@validate="(value) => onValidate('repoUrl', value)" @validate="(value: boolean) => onValidate('repoUrl', value)"
/> />
<n8n-button <n8n-button
v-if="isConnected" v-if="isConnected"
@ -248,7 +248,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="keyGeneratorTypeValidationRules" :validation-rules="keyGeneratorTypeValidationRules"
:options="sourceControlStore.sshKeyTypesWithLabel" :options="sourceControlStore.sshKeyTypesWithLabel"
:model-value="sourceControlStore.preferences.keyGeneratorType" :model-value="sourceControlStore.preferences.keyGeneratorType"
@validate="(value) => onValidate('keyGeneratorType', value)" @validate="(value: boolean) => onValidate('keyGeneratorType', value)"
@update:model-value="onSelectSshKeyType" @update:model-value="onSelectSshKeyType"
/> />
<CopyInput <CopyInput
@ -309,7 +309,7 @@ const onSelectSshKeyType = async (sshKeyType: TupleToUnion<SshKeyTypes>) => {
:validation-rules="branchNameValidationRules" :validation-rules="branchNameValidationRules"
:options="branchNameOptions" :options="branchNameOptions"
:model-value="sourceControlStore.preferences.branchName" :model-value="sourceControlStore.preferences.branchName"
@validate="(value) => onValidate('branchName', value)" @validate="(value: boolean) => onValidate('branchName', value)"
@update:model-value="onSelect" @update:model-value="onSelect"
/> />
<n8n-tooltip placement="top"> <n8n-tooltip placement="top">

View file

@ -84,7 +84,7 @@ import { defineComponent } from 'vue';
import { mapStores } from 'pinia'; import { mapStores } from 'pinia';
import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS, ROLE } from '@/constants'; import { EnterpriseEditionFeature, INVITE_USER_MODAL_KEY, VIEWS, ROLE } from '@/constants';
import type { IUser, IUserListAction, InvitableRoleName } from '@/Interface'; import type { IRole, IUser, IUserListAction, InvitableRoleName } from '@/Interface';
import { useToast } from '@/composables/useToast'; import { useToast } from '@/composables/useToast';
import { useUIStore } from '@/stores/ui.store'; import { useUIStore } from '@/stores/ui.store';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';

View file

@ -15,7 +15,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { Route } from 'vue-router'; import type { RouteLocationPathRaw } from 'vue-router';
import { VIEWS } from '@/constants'; import { VIEWS } from '@/constants';
import SettingsSidebar from '@/components/SettingsSidebar.vue'; import SettingsSidebar from '@/components/SettingsSidebar.vue';
@ -32,7 +32,7 @@ const SettingsView = defineComponent({
}, },
data() { data() {
return { return {
previousRoute: null as Route | null, previousRoute: null as RouteLocationPathRaw | null,
}; };
}, },
methods: { methods: {

View file

@ -101,9 +101,9 @@ export default defineComponent({
}, },
computed: { computed: {
...mapStores(useUIStore, useUsersStore), ...mapStores(useUIStore, useUsersStore),
inviteMessage(): null | string { inviteMessage(): string {
if (!this.inviter) { if (!this.inviter) {
return null; return '';
} }
return this.$locale.baseText('settings.signup.signUpInviterInfo', { return this.$locale.baseText('settings.signup.signUpInviterInfo', {

View file

@ -53,15 +53,18 @@ const canRender = ref(true);
const isListLoading = ref(true); const isListLoading = ref(true);
const requestNumberOfItems = ref(20); const requestNumberOfItems = ref(20);
const lastReceivedItemsLength = ref(0); const lastReceivedItemsLength = ref(0);
const editorRoute = computed(() => ({
name: VIEWS.WORKFLOW,
params: {
name: route.params.workflowId,
},
}));
const activeWorkflow = ref<IWorkflowDb | null>(null); const activeWorkflow = ref<IWorkflowDb | null>(null);
const workflowHistory = ref<WorkflowHistory[]>([]); const workflowHistory = ref<WorkflowHistory[]>([]);
const activeWorkflowVersion = ref<WorkflowVersion | null>(null); const activeWorkflowVersion = ref<WorkflowVersion | null>(null);
const workflowId = computed(() => normalizeSingleRouteParam('workflowId'));
const versionId = computed(() => normalizeSingleRouteParam('versionId'));
const editorRoute = computed(() => ({
name: VIEWS.WORKFLOW,
params: {
name: workflowId.value,
},
}));
const actions = computed<UserAction[]>(() => const actions = computed<UserAction[]>(() =>
workflowHistoryActionTypes.map((value) => ({ workflowHistoryActionTypes.map((value) => ({
label: i18n.baseText(`workflowHistory.item.actions.${value}`), label: i18n.baseText(`workflowHistory.item.actions.${value}`),
@ -70,23 +73,18 @@ const actions = computed<UserAction[]>(() =>
})), })),
); );
const isFirstItemShown = computed( const isFirstItemShown = computed(() => workflowHistory.value[0]?.versionId === versionId.value);
() => workflowHistory.value[0]?.versionId === route.params.versionId,
);
const evaluatedPruneTime = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24)); const evaluatedPruneTime = computed(() => Math.floor(workflowHistoryStore.evaluatedPruneTime / 24));
const sendTelemetry = (event: string) => { const sendTelemetry = (event: string) => {
telemetry.track(event, { telemetry.track(event, {
instance_id: useRootStore().instanceId, instance_id: useRootStore().instanceId,
workflow_id: route.params.workflowId, workflow_id: workflowId.value,
}); });
}; };
const loadMore = async (queryParams: WorkflowHistoryRequestParams) => { const loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
const history = await workflowHistoryStore.getWorkflowHistory( const history = await workflowHistoryStore.getWorkflowHistory(workflowId.value, queryParams);
route.params.workflowId,
queryParams,
);
lastReceivedItemsLength.value = history.length; lastReceivedItemsLength.value = history.length;
workflowHistory.value = workflowHistory.value.concat(history); workflowHistory.value = workflowHistory.value.concat(history);
}; };
@ -95,17 +93,17 @@ onBeforeMount(async () => {
sendTelemetry('User opened workflow history'); sendTelemetry('User opened workflow history');
try { try {
const [workflow] = await Promise.all([ const [workflow] = await Promise.all([
workflowsStore.fetchWorkflow(route.params.workflowId), workflowsStore.fetchWorkflow(workflowId.value),
loadMore({ take: requestNumberOfItems.value }), loadMore({ take: requestNumberOfItems.value }),
]); ]);
activeWorkflow.value = workflow; activeWorkflow.value = workflow;
isListLoading.value = false; isListLoading.value = false;
if (!route.params.versionId && workflowHistory.value.length) { if (!versionId.value && workflowHistory.value.length) {
await router.replace({ await router.replace({
name: VIEWS.WORKFLOW_HISTORY, name: VIEWS.WORKFLOW_HISTORY,
params: { params: {
workflowId: route.params.workflowId, workflowId: workflowId.value,
versionId: workflowHistory.value[0].versionId, versionId: workflowHistory.value[0].versionId,
}, },
}); });
@ -116,11 +114,17 @@ onBeforeMount(async () => {
} }
}); });
const normalizeSingleRouteParam = (name: string): string => {
const param = route.params[name];
if (typeof param === 'string') return param;
return param?.[0] ?? '';
};
const openInNewTab = (id: WorkflowVersionId) => { const openInNewTab = (id: WorkflowVersionId) => {
const { href } = router.resolve({ const { href } = router.resolve({
name: VIEWS.WORKFLOW_HISTORY, name: VIEWS.WORKFLOW_HISTORY,
params: { params: {
workflowId: route.params.workflowId, workflowId: workflowId.value,
versionId: id, versionId: id,
}, },
}); });
@ -183,7 +187,7 @@ const cloneWorkflowVersion = async (
data: { formattedCreatedAt: string }, data: { formattedCreatedAt: string },
) => { ) => {
const clonedWorkflow = await workflowHistoryStore.cloneIntoNewWorkflow( const clonedWorkflow = await workflowHistoryStore.cloneIntoNewWorkflow(
route.params.workflowId, workflowId.value,
id, id,
data, data,
); );
@ -210,17 +214,17 @@ const restoreWorkflowVersion = async (
id: WorkflowVersionId, id: WorkflowVersionId,
data: { formattedCreatedAt: string }, data: { formattedCreatedAt: string },
) => { ) => {
const workflow = await workflowsStore.fetchWorkflow(route.params.workflowId); const workflow = await workflowsStore.fetchWorkflow(workflowId.value);
const modalAction = await openRestorationModal(workflow.active, data.formattedCreatedAt); const modalAction = await openRestorationModal(workflow.active, data.formattedCreatedAt);
if (modalAction === WorkflowHistoryVersionRestoreModalActions.cancel) { if (modalAction === WorkflowHistoryVersionRestoreModalActions.cancel) {
return; return;
} }
activeWorkflow.value = await workflowHistoryStore.restoreWorkflow( activeWorkflow.value = await workflowHistoryStore.restoreWorkflow(
route.params.workflowId, workflowId.value,
id, id,
modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore, modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore,
); );
const history = await workflowHistoryStore.getWorkflowHistory(route.params.workflowId, { const history = await workflowHistoryStore.getWorkflowHistory(workflowId.value, {
take: 1, take: 1,
}); });
workflowHistory.value = history.concat(workflowHistory.value); workflowHistory.value = history.concat(workflowHistory.value);
@ -246,7 +250,7 @@ const onAction = async ({
sendTelemetry('User opened version in new tab'); sendTelemetry('User opened version in new tab');
break; break;
case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD: case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data); await workflowHistoryStore.downloadVersion(workflowId.value, id, data);
sendTelemetry('User downloaded version'); sendTelemetry('User downloaded version');
break; break;
case WORKFLOW_HISTORY_ACTIONS.CLONE: case WORKFLOW_HISTORY_ACTIONS.CLONE:
@ -278,7 +282,7 @@ const onPreview = async ({ event, id }: { event: MouseEvent; id: WorkflowVersion
await router.push({ await router.push({
name: VIEWS.WORKFLOW_HISTORY, name: VIEWS.WORKFLOW_HISTORY,
params: { params: {
workflowId: route.params.workflowId, workflowId: workflowId.value,
versionId: id, versionId: id,
}, },
}); });
@ -290,24 +294,24 @@ const onUpgrade = () => {
}; };
watchEffect(async () => { watchEffect(async () => {
if (!route.params.versionId) { if (!versionId.value) {
return; return;
} }
try { try {
activeWorkflowVersion.value = await workflowHistoryStore.getWorkflowVersion( activeWorkflowVersion.value = await workflowHistoryStore.getWorkflowVersion(
route.params.workflowId, workflowId.value,
route.params.versionId, versionId.value,
); );
sendTelemetry('User selected version'); sendTelemetry('User selected version');
} catch (error) { } catch (error) {
toast.showError( toast.showError(
new Error(`${error.message} "${route.params.versionId}"&nbsp;`), new Error(`${error.message} "${versionId.value}"&nbsp;`),
i18n.baseText('workflowHistory.title'), i18n.baseText('workflowHistory.title'),
); );
} }
try { try {
activeWorkflow.value = await workflowsStore.fetchWorkflow(route.params.workflowId); activeWorkflow.value = await workflowsStore.fetchWorkflow(workflowId.value);
} catch (error) { } catch (error) {
canRender.value = false; canRender.value = false;
toast.showError(error, i18n.baseText('workflowHistory.title')); toast.showError(error, i18n.baseText('workflowHistory.title'));

View file

@ -9,7 +9,7 @@ import { useRoute, useRouter } from 'vue-router';
const loadingService = useLoadingService(); const loadingService = useLoadingService();
const templateStore = useTemplatesStore(); const templateStore = useTemplatesStore();
const workfowStore = useWorkflowsStore(); const workflowsStore = useWorkflowsStore();
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const i18n = useI18n(); const i18n = useI18n();
@ -26,7 +26,7 @@ const openWorkflowTemplate = async (templateId: string) => {
interpolate: { name: template.name }, interpolate: { name: template.name },
}); });
const workflow = await workfowStore.createNewWorkflow({ const workflow = await workflowsStore.createNewWorkflow({
name, name,
connections: template.workflow.connections, connections: template.workflow.connections,
nodes: template.workflow.nodes, nodes: template.workflow.nodes,

View file

@ -67,12 +67,11 @@
<div class="text-center mt-s"> <div class="text-center mt-s">
<n8n-heading tag="h2" size="xlarge" class="mb-2xs"> <n8n-heading tag="h2" size="xlarge" class="mb-2xs">
{{ {{
$locale.baseText( currentUser.firstName
currentUser.firstName ? $locale.baseText('workflows.empty.heading', {
? 'workflows.empty.heading' interpolate: { name: currentUser.firstName },
: 'workflows.empty.heading.userNotSetup', })
{ interpolate: { name: currentUser.firstName } }, : $locale.baseText('workflows.empty.heading.userNotSetup')
)
}} }}
</n8n-heading> </n8n-heading>
<n8n-text size="large" color="text-base"> <n8n-text size="large" color="text-base">
@ -178,13 +177,13 @@ import { useSourceControlStore } from '@/stores/sourceControl.store';
import { useTagsStore } from '@/stores/tags.store'; import { useTagsStore } from '@/stores/tags.store';
import { useProjectsStore } from '@/features/projects/projects.store'; import { useProjectsStore } from '@/features/projects/projects.store';
import ProjectTabs from '@/features/projects/components/ProjectTabs.vue'; import ProjectTabs from '@/features/projects/components/ProjectTabs.vue';
import { useTemplatesStore } from '@/stores/templates.store';
type IResourcesListLayoutInstance = InstanceType<typeof ResourcesListLayout>; type IResourcesListLayoutInstance = InstanceType<typeof ResourcesListLayout>;
interface Filters { interface Filters {
search: string; search: string;
ownedBy: string; homeProject: string;
sharedWith: string;
status: string | boolean; status: string | boolean;
tags: string[]; tags: string[];
} }
@ -210,9 +209,9 @@ const WorkflowsView = defineComponent({
filters: { filters: {
search: '', search: '',
homeProject: '', homeProject: '',
status: StatusFilter.ALL as string | boolean, status: StatusFilter.ALL,
tags: [] as string[], tags: [],
}, } as Filters,
sourceControlStoreUnsubscribe: () => {}, sourceControlStoreUnsubscribe: () => {},
}; };
}, },
@ -225,6 +224,7 @@ const WorkflowsView = defineComponent({
useSourceControlStore, useSourceControlStore,
useTagsStore, useTagsStore,
useProjectsStore, useProjectsStore,
useTemplatesStore,
), ),
readOnlyEnv(): boolean { readOnlyEnv(): boolean {
return this.sourceControlStore.preferences.branchReadOnly; return this.sourceControlStore.preferences.branchReadOnly;
@ -258,10 +258,18 @@ const WorkflowsView = defineComponent({
return this.uiStore.suggestedTemplates; return this.uiStore.suggestedTemplates;
}, },
userRole() { userRole() {
const userRole: string | undefined = const role = this.usersStore.currentUserCloudInfo?.role;
this.usersStore.currentUserCloudInfo?.role ??
this.usersStore.currentUser?.personalizationAnswers?.role; if (role) {
return userRole; return role;
}
const answers = this.usersStore.currentUser?.personalizationAnswers;
if (answers && 'role' in answers) {
return answers.role;
}
return undefined;
}, },
isSalesUser() { isSalesUser() {
if (!this.userRole) { if (!this.userRole) {