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) => ({