diff --git a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx
index 1aef638ffd..1fac951953 100644
--- a/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx
+++ b/Composer/packages/client/src/components/CreationFlow/v2/CreateOptions.tsx
@@ -20,9 +20,9 @@ import { useRecoilValue } from 'recoil';
import { DialogCreationCopy } from '../../../constants';
import { getAliasFromPayload, isElectron } from '../../../utils/electronUtil';
import { userHasNodeInstalledState } from '../../../recoilModel';
+import { InstallDepModal } from '../../InstallDepModal';
import { CreateBotV2 } from './CreateBot';
-import { NodeModal } from './NodeModal';
// -------------------- CreateOptions -------------------- //
type CreateOptionsProps = {
@@ -135,7 +135,17 @@ export function CreateOptionsV2(props: CreateOptionsProps) {
onDismiss={onDismiss}
onNext={onNext}
/>
- {isElectron() && showNodeModal && }
+ {isElectron() && showNodeModal && (
+ setShowNodeModal(false)}
+ />
+ )}
);
}
diff --git a/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx b/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx
deleted file mode 100644
index 0feaa94798..0000000000
--- a/Composer/packages/client/src/components/CreationFlow/v2/NodeModal.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable react/no-danger */
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-/** @jsx jsx */
-import { DialogTypes, DialogWrapper } from '@bfc/ui-shared/lib/components/DialogWrapper';
-import { jsx } from '@emotion/core';
-import formatMessage from 'format-message';
-import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/components/Button';
-import { DialogFooter } from 'office-ui-fabric-react/lib/components/Dialog';
-import React from 'react';
-import { Text } from 'office-ui-fabric-react/lib/Text';
-import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
-
-const dialogFooterClass = mergeStyles({
- marginTop: '25px',
-});
-
-type NodeModalProps = {
- setIsOpen: Function;
- isOpen: boolean;
-};
-
-export const NodeModal: React.FC = (props) => {
- return (
- {
- props.setIsOpen(false);
- }}
- >
-
- {formatMessage(
- 'Bot Framework Composer requires Node.js in order to create and run a new bot. Click “Install Node.js” to install the latest version'
- )}
-
-
-
- {
- props.setIsOpen(false);
- }}
- />
-
-
- );
-};
diff --git a/Composer/packages/client/src/components/InstallDepModal.tsx b/Composer/packages/client/src/components/InstallDepModal.tsx
new file mode 100644
index 0000000000..283497c88e
--- /dev/null
+++ b/Composer/packages/client/src/components/InstallDepModal.tsx
@@ -0,0 +1,52 @@
+/* eslint-disable react/no-danger */
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/** @jsx jsx */
+import { DialogTypes, DialogWrapper } from '@bfc/ui-shared/lib/components/DialogWrapper';
+import { jsx } from '@emotion/core';
+import formatMessage from 'format-message';
+import { DefaultButton, PrimaryButton } from 'office-ui-fabric-react/lib/components/Button';
+import { DialogFooter } from 'office-ui-fabric-react/lib/components/Dialog';
+import React from 'react';
+import { Text } from 'office-ui-fabric-react/lib/Text';
+import { Link } from 'office-ui-fabric-react/lib/Link';
+
+type InstallDepModalProps = {
+ onDismiss: () => void;
+ title: string;
+ text: React.ReactNode;
+ downloadLink: string;
+ downloadLinkText: string;
+ learnMore?: {
+ text: string;
+ link: string;
+ };
+};
+
+export const InstallDepModal: React.FC = (props) => {
+ return (
+
+
+ {props.text}
+ {props.learnMore && (
+
+
+
+ {props.learnMore.text}
+
+
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/Composer/packages/client/src/pages/home/Home.tsx b/Composer/packages/client/src/pages/home/Home.tsx
index 5342f71c2d..d51d4eb52d 100644
--- a/Composer/packages/client/src/pages/home/Home.tsx
+++ b/Composer/packages/client/src/pages/home/Home.tsx
@@ -21,12 +21,15 @@ import {
feedState,
templateIdState,
currentProjectIdState,
+ warnAboutDotNetState,
} from '../../recoilModel/atoms/appState';
import TelemetryClient from '../../telemetry/TelemetryClient';
import composerDocumentIcon from '../../images/composerDocumentIcon.svg';
import stackoverflowIcon from '../../images/stackoverflowIcon.svg';
import githubIcon from '../../images/githubIcon.svg';
import noRecentBotsCover from '../../images/noRecentBotsCover.svg';
+import { InstallDepModal } from '../../components/InstallDepModal';
+import { missingDotnetVersionError } from '../../utils/runtimeErrors';
import { RecentBotList } from './RecentBotList';
import { WhatsNewsList } from './WhatsNewsList';
@@ -70,7 +73,10 @@ const Home: React.FC = () => {
const recentProjects = useRecoilValue(recentProjectsState);
const feed = useRecoilValue(feedState);
const templateId = useRecoilValue(templateIdState);
- const { openProject, setCreationFlowStatus, setCreationFlowType } = useRecoilValue(dispatcherState);
+ const { openProject, setCreationFlowStatus, setCreationFlowType, setWarnAboutDotNet } = useRecoilValue(
+ dispatcherState
+ );
+ const warnAboutDotNet = useRecoilValue(warnAboutDotNetState);
const onItemChosen = async (item) => {
if (item?.path) {
@@ -234,6 +240,16 @@ const Home: React.FC = () => {
+ {warnAboutDotNet && (
+ setWarnAboutDotNet(false)}
+ />
+ )}
);
};
diff --git a/Composer/packages/client/src/recoilModel/atoms/appState.ts b/Composer/packages/client/src/recoilModel/atoms/appState.ts
index c8de7d826a..ab34dd19b9 100644
--- a/Composer/packages/client/src/recoilModel/atoms/appState.ts
+++ b/Composer/packages/client/src/recoilModel/atoms/appState.ts
@@ -349,3 +349,8 @@ export const userHasNodeInstalledState = atom({
key: getFullyQualifiedKey('userHasNodeInstalled'),
default: true,
});
+
+export const warnAboutDotNetState = atom({
+ key: getFullyQualifiedKey('warnAboutDotNetState'),
+ default: false,
+});
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
index 0fe2c2aeb4..e6f7c222a1 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/project.ts
@@ -36,6 +36,7 @@ import {
projectMetaDataState,
selectedTemplateReadMeState,
showCreateQnAFromUrlDialogState,
+ warnAboutDotNetState,
settingsState,
} from '../atoms';
import { botRuntimeOperationsSelector, rootBotProjectIdSelector } from '../selectors';
@@ -663,6 +664,10 @@ export const projectDispatcher = () => {
setError(callbackHelpers, error);
});
+ const setWarnAboutDotNet = useRecoilCallback((callbackHelpers: CallbackInterface) => (warn: boolean) => {
+ callbackHelpers.set(warnAboutDotNetState, warn);
+ });
+
return {
openProject,
createNewBot,
@@ -686,6 +691,7 @@ export const projectDispatcher = () => {
updateCreationMessage,
setCurrentProjectId,
setProjectError,
+ setWarnAboutDotNet,
fetchReadMe,
};
};
diff --git a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
index 21115bf329..259276ff00 100644
--- a/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
+++ b/Composer/packages/client/src/recoilModel/dispatchers/utils/project.ts
@@ -33,6 +33,7 @@ import { CallbackInterface } from 'recoil';
import { v4 as uuid } from 'uuid';
import isEmpty from 'lodash/isEmpty';
+import { checkIfDotnetVersionMissing } from '../../../utils/runtimeErrors';
import { BASEURL, BotStatus } from '../../../constants';
import settingStorage from '../../../utils/dialogSettingStorage';
import { getUniqueName } from '../../../utils/fileUtil';
@@ -73,6 +74,7 @@ import {
createQnAOnState,
botEndpointsState,
dispatcherState,
+ warnAboutDotNetState,
} from '../../atoms';
import * as botstates from '../../atoms/botState';
import lgWorker from '../../parsers/lgWorker';
@@ -377,8 +379,17 @@ export const fetchProjectDataById = async (projectId): Promise<{ botFiles: any;
}
};
-export const handleProjectFailure = (callbackHelpers: CallbackInterface, ex) => {
- setError(callbackHelpers, ex);
+export const handleProjectFailure = (callbackHelpers: CallbackInterface, error) => {
+ const isDotnetError = checkIfDotnetVersionMissing({
+ message: error.response?.data?.message ?? error.message ?? '',
+ });
+
+ if (isDotnetError) {
+ callbackHelpers.set(warnAboutDotNetState, true);
+ } else {
+ callbackHelpers.set(warnAboutDotNetState, false);
+ setError(callbackHelpers, error);
+ }
};
export const processSchema = (projectId: string, schema: any) => ({