From 89ab91361e1daac9ce54fdb1c080387aa52ade18 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Tue, 28 Apr 2020 10:47:59 +0000 Subject: [PATCH] fix: provider cards ui fixes - alignment of provider cards in api home screen fixes - do not load the providers if already present - fix 'Add to page' button moving on cliick in provider templates --- app/client/src/actions/apiPaneActions.ts | 27 +++ app/client/src/actions/providerActions.ts | 9 +- app/client/src/api/ProvidersApi.ts | 25 +- .../form/CredentialsTooltip.tsx | 126 ++++++++++ app/client/src/constants/Colors.tsx | 1 + .../src/constants/ReduxActionConstants.tsx | 9 + app/client/src/constants/providerConstants.ts | 13 + app/client/src/constants/routes.ts | 8 +- .../pages/Editor/APIEditor/ApiHomeScreen.tsx | 229 ++++++++++++------ .../src/pages/Editor/APIEditor/Form.tsx | 13 + .../Editor/APIEditor/ProviderTemplates.tsx | 132 +++++++--- .../Editor/APIEditor/RapidApiEditorForm.tsx | 42 +++- .../src/pages/Editor/APIEditor/index.tsx | 3 + .../src/reducers/uiReducers/apiPaneReducer.ts | 27 +++ .../reducers/uiReducers/providerReducer.ts | 34 ++- app/client/src/sagas/ApiPaneSagas.ts | 41 +++- app/client/src/sagas/ProvidersSaga.ts | 35 +++ app/client/src/selectors/editorSelectors.tsx | 2 + app/client/src/utils/AppsmithUtils.tsx | 30 +++ 19 files changed, 674 insertions(+), 132 deletions(-) create mode 100644 app/client/src/components/editorComponents/form/CredentialsTooltip.tsx diff --git a/app/client/src/actions/apiPaneActions.ts b/app/client/src/actions/apiPaneActions.ts index d968f86042..82561a2734 100644 --- a/app/client/src/actions/apiPaneActions.ts +++ b/app/client/src/actions/apiPaneActions.ts @@ -14,6 +14,33 @@ export const initApiPane = (urlId?: string): ReduxAction<{ id?: string }> => { }; }; +export const setCurrentCategory = ( + category: string, +): ReduxAction<{ category: string }> => { + return { + type: ReduxActionTypes.SET_CURRENT_CATEGORY, + payload: { category }, + }; +}; + +export const setLastUsedEditorPage = ( + path: string, +): ReduxAction<{ path: string }> => { + return { + type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE, + payload: { path }, + }; +}; + +export const setLastSelectedPage = ( + selectedPageId: string, +): ReduxAction<{ selectedPageId: string }> => { + return { + type: ReduxActionTypes.SET_LAST_SELECTED_PAGE_PAGE, + payload: { selectedPageId }, + }; +}; + export const createNewApiAction = ( pageId: string, ): ReduxAction<{ pageId: string }> => ({ diff --git a/app/client/src/actions/providerActions.ts b/app/client/src/actions/providerActions.ts index 2ac702309a..81fdcbcf54 100644 --- a/app/client/src/actions/providerActions.ts +++ b/app/client/src/actions/providerActions.ts @@ -17,9 +17,16 @@ export const fetchProviderCategories = () => { }; }; -export const fetchProviderTemplates = () => { +export const getProviderDetailsByProviderId = (providerId: string) => { + return { + type: ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT, + payload: { providerId }, + }; +}; +export const fetchProviderTemplates = (providerId: string) => { return { type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT, + payload: { providerId }, }; }; diff --git a/app/client/src/api/ProvidersApi.ts b/app/client/src/api/ProvidersApi.ts index 0006c2da45..c76d52856d 100644 --- a/app/client/src/api/ProvidersApi.ts +++ b/app/client/src/api/ProvidersApi.ts @@ -1,12 +1,20 @@ import { AxiosPromise } from "axios"; import Api from "./Api"; import { ApiResponse } from "./ApiResponses"; -import { Providers, ProviderTemplates } from "constants/providerConstants"; +import { + Providers, + ProviderTemplates, + ProvidersDataArray, +} from "constants/providerConstants"; export interface FetchProvidersResponse extends ApiResponse { data: Providers; } +export interface FetchProviderDetailsResponse extends ApiResponse { + data: ProvidersDataArray; +} + export interface FetchProviderCategoriesResponse extends ApiResponse { data: string[]; } @@ -19,6 +27,10 @@ export interface FetchProviderTemplatesRequest { providerId: string; } +export interface FetchProviderDetailsByProviderIdRequest { + providerId: string; +} + export interface FetchProviderWithCategoryRequest { category: string; page: number; @@ -34,6 +46,10 @@ export class ProvidersApi extends Api { static providersURL = "v1/providers"; static providerCategoriesURL = "v1/providers/categories"; + static providerDetailsByIdURL = (providerId: string) => { + return `v1/marketplace/providers/${providerId}`; + }; + static providerTemplateURL = (providerId: string) => { return `v1/marketplace/templates?providerId=${providerId}`; }; @@ -73,6 +89,13 @@ export class ProvidersApi extends Api { ProvidersApi.providersWithCategoryURL(request.category, page), ); } + + static fetchProviderDetailsByProviderId( + request: FetchProviderDetailsByProviderIdRequest, + ): AxiosPromise { + const { providerId } = request; + return Api.get(ProvidersApi.providerDetailsByIdURL(providerId)); + } } export default ProvidersApi; diff --git a/app/client/src/components/editorComponents/form/CredentialsTooltip.tsx b/app/client/src/components/editorComponents/form/CredentialsTooltip.tsx new file mode 100644 index 0000000000..cfaf653101 --- /dev/null +++ b/app/client/src/components/editorComponents/form/CredentialsTooltip.tsx @@ -0,0 +1,126 @@ +import React from "react"; +import { Popover, PopoverInteractionKind } from "@blueprintjs/core"; +import styled, { createGlobalStyle } from "styled-components"; +import { Colors } from "constants/Colors"; +import { FormIcons } from "icons/FormIcons"; + +const CredentialTooltipWrapper = styled.div` + .credentialTooltipContainer { + display: flex; + margin-left: 5px; + } + .infoIconDiv { + margin-top: -2px; + margin-left: 2px; + } + .credentialTitle { + color: ${Colors.CADET_BLUE}; + } +`; + +const TooltipStyles = createGlobalStyle` + .credentials-tooltip{ + .bp3-popover { + box-shadow: none; + max-width: 340px; + .bp3-popover-arrow { + display: block; + fill: none; + } + .bp3-popover-arrow-fill { + fill: ${Colors.BLUE_CHARCOAL}; + } + .bp3-popover-content { + padding: 15px; + background-color: ${Colors.BLUE_CHARCOAL}; + max-height: 300px; + overflow: auto; + color: ${Colors.WHITE}; + text-align: left; + border-radius: 4px; + text-transform: initial; + font-weight: 500; + font-size: 14px; + line-height: 18px; + } + } + } +`; + +const IconContainer = styled.div` + .bp3-icon { + border-radius: 4px 0 0 4px; + margin: 0; + height: 30px; + width: 30px; + display: flex; + align-items: center; + justify-content: center; + background-color: ${Colors.AQUA_HAZE}; + svg { + height: 20px; + width: 20px; + path { + fill: #979797; + } + } + } + .bp3-popover-target { + padding-right: 10px; + } +`; + +interface Props { + providerCredentialSteps: string; +} + +const HelperTooltip = (props: Props) => { + return ( + +
+
+ How to get Credentials? +
+
+ + + How to get credentials: +

") + .split("\\n") + .join("

"), + }} + >

+
+ } + position="bottom" + defaultIsOpen={false} + interactionKind={PopoverInteractionKind.HOVER} + usePortal + portalClassName="credentials-tooltip" + > + + + + +
+ +
+ ); +}; + +export default HelperTooltip; diff --git a/app/client/src/constants/Colors.tsx b/app/client/src/constants/Colors.tsx index 07b5f3e88a..dfff0cf0a2 100644 --- a/app/client/src/constants/Colors.tsx +++ b/app/client/src/constants/Colors.tsx @@ -41,6 +41,7 @@ export const Colors: Record = { BUTTER_CUP: "#F7AF22", BLUE_CHARCOAL: "#23292E", TROUT: "#4C565E", + JAFFA_DARK: "#EF7541", }; export type Color = typeof Colors[keyof typeof Colors]; diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index bac0054f33..5a5a6ee0a3 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -185,6 +185,13 @@ export const ReduxActionTypes: { [key: string]: string } = { BATCHED_UPDATE: "BATCHED_UPDATE", EXECUTE_BATCH: "EXECUTE_BATCH", CREATE_NEW_API_ACTION: "CREATE_NEW_API_ACTION", + SET_CURRENT_CATEGORY: "SET_CURRENT_CATEGORY", + SET_LAST_USED_EDITOR_PAGE: "SET_LAST_USED_EDITOR_PAGE", + SET_LAST_SELECTED_PAGE_PAGE: "SET_LAST_SELECTED_PAGE_PAGE", + FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT: + "FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT", + FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS: + "FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; @@ -259,6 +266,8 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { FETCH_PROVIDERS_CATEGORIES_ERROR: "FETCH_PROVIDERS_CATEGORIES_ERROR", FETCH_PROVIDERS_WITH_CATEGORY_ERROR: "FETCH_PROVIDERS_WITH_CATEGORY_ERROR", CREATE_MODAL_ERROR: "CREATE_MODAL_ERROR", + FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR: + "FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR", }; export const ReduxFormActionTypes: { [key: string]: string } = { diff --git a/app/client/src/constants/providerConstants.ts b/app/client/src/constants/providerConstants.ts index af03e734bd..7a3b7b4197 100644 --- a/app/client/src/constants/providerConstants.ts +++ b/app/client/src/constants/providerConstants.ts @@ -12,6 +12,10 @@ export type ProvidersCategoriesResponse = ApiResponse & { data: string[]; }; +export type FetchProviderDetailsResponse = ApiResponse & { + data: ProvidersDataArray; +}; + export type Providers = ApiResponse & { providers: ProvidersDataArray[]; total: number; @@ -56,3 +60,12 @@ export type ProviderTemplateArray = ApiResponse & { }; export const DEFAULT_TEMPLATE_TYPE = "TEMPLATE"; + +export const providerBackgroundColors = [ + "#5F60B4", + "#0BA780", + "#929500", + "#1F97D3", + "#B32FA5", + "#1A29B1", +]; diff --git a/app/client/src/constants/routes.ts b/app/client/src/constants/routes.ts index f4bbfaee75..ee8bdf2ad3 100644 --- a/app/client/src/constants/routes.ts +++ b/app/client/src/constants/routes.ts @@ -26,7 +26,6 @@ export type ProviderViewerRouteParams = { applicationId: string; pageId: string; providerId: string; - destinationPageId: string; }; export const BUILDER_BASE_URL = (applicationId = ":applicationId"): string => @@ -122,12 +121,7 @@ export const getProviderTemplatesURL = ( applicationId = ":applicationId", pageId = ":pageId", providerId = ":providerId", - destinationPageId = ":destinationPageId", -): string => - `${API_EDITOR_URL( - applicationId, - pageId, - )}/provider/${providerId}/?importTo=${destinationPageId}`; +): string => `${API_EDITOR_URL(applicationId, pageId)}/provider/${providerId}`; export const EDITOR_ROUTES = [ { diff --git a/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx b/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx index f1c2222c06..247ac2013f 100644 --- a/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx +++ b/app/client/src/pages/Editor/APIEditor/ApiHomeScreen.tsx @@ -41,7 +41,12 @@ import Spinner from "components/editorComponents/Spinner"; import CurlLogo from "assets/images/Curl-logo.svg"; import { FetchProviderWithCategoryRequest } from "api/ProvidersApi"; import { Plugin } from "api/PluginApi"; -import { createNewApiAction } from "actions/apiPaneActions"; +import { + createNewApiAction, + setCurrentCategory, + setLastUsedEditorPage, +} from "actions/apiPaneActions"; +import { getInitialsAndColorCode } from "utils/AppsmithUtils"; // const SearchContainer = styled.div` // display: flex; @@ -110,24 +115,14 @@ const StyledContainer = styled.div` color: ${Colors.OXFORD_BLUE}; text-decoration: none; } -`; - -const ApiCard = styled.div` - flex: 1; - display: inline-flex; - flex-wrap: wrap; - margin-left: -10px; - justify-content: flex-start; - text-align: center; - min-width: 150px; - border-radius: 4px; - .apiImage { + object-fit: contain; height: 50px; width: auto; max-width: 100%; margin-top: -5px; margin-bottom: 10px; + min-height: 50px; } .curlImage { width: 60px; @@ -143,7 +138,29 @@ const ApiCard = styled.div` height: 110px; padding-bottom: 0px; cursor: pointer; + border: 1px solid #e6e6e6; + box-shadow: none; } + .eachCard:active { + border: 1px solid ${Colors.JAFFA_DARK}; + background: rgba(242, 153, 74, 0.17); + } + .eachCard:hover { + border: 1px solid ${Colors.JAFFA_DARK}; + } +`; + +const ApiCard = styled.div` + flex: 1; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + column-gap: 10px; + flex-wrap: wrap; + margin-left: -10px; + text-align: center; + min-width: 150px; + border-radius: 4px; + width: 100%; `; const CardList = styled.div` @@ -153,6 +170,15 @@ const CardList = styled.div` height: 110px; padding-bottom: 0px; cursor: pointer; + border: 1px solid #e6e6e6; + box-shadow: none; + } + .eachProviderCard:active { + border: 1px solid ${Colors.JAFFA_DARK}; + background: rgba(242, 153, 74, 0.17); + } + .eachProviderCard:hover { + border: 1px solid ${Colors.JAFFA_DARK}; } `; @@ -196,7 +222,7 @@ const DropdownSelect = styled.div` font-size: 14px; float: right; width: 232px; - margin-right: 8%; + margin-right: 25px; margin-bottom: 25px; `; @@ -210,6 +236,9 @@ const PageLoadingContainer = styled(CenteredWrapper)` `; type ApiHomeScreenProps = { + initialValues: { + category: string; + }; currentCategory: string; importedCollections: TemplateList[]; fetchImportedCollections: () => void; @@ -229,6 +258,9 @@ type ApiHomeScreenProps = { location: { search: string; }; + match: { + url: string; + }; history: { replace: (data: string) => void; push: (data: string) => void; @@ -237,13 +269,17 @@ type ApiHomeScreenProps = { providersTotal: number; isSwitchingCategory: boolean; createNewApiAction: (pageId: string) => void; + setCurrentCategory: (category: string) => void; + setLastUsedEditorPage: (path: string) => void; + previouslySetCategory: string; }; type ApiHomeScreenState = { page: number; }; -type Props = ApiHomeScreenProps & InjectedFormProps; +type Props = ApiHomeScreenProps & + InjectedFormProps<{ category: string }, ApiHomeScreenProps>; class ApiHomeScreen extends React.Component { constructor(props: Props) { @@ -255,21 +291,34 @@ class ApiHomeScreen extends React.Component { } componentDidMount() { - const { importedCollections } = this.props; - this.props.fetchProviderCategories(); + const { + importedCollections, + providersTotal, + providerCategories, + } = this.props; + if (providerCategories.length === 0) { + this.props.fetchProviderCategories(); + } if (importedCollections.length === 0) { this.props.fetchImportedCollections(); } - this.props.clearProviders(); - this.props.change("category", DEFAULT_PROVIDER_OPTION); - this.props.fetchProvidersWithCategory({ - category: DEFAULT_PROVIDER_OPTION, - page: 1, - }); + if (!providersTotal) { + this.props.clearProviders(); + this.props.change("category", DEFAULT_PROVIDER_OPTION); + this.props.fetchProvidersWithCategory({ + category: DEFAULT_PROVIDER_OPTION, + page: 1, + }); + } + this.props.setLastUsedEditorPage(this.props.match.url); } componentDidUpdate(prevProps: Props) { - if (prevProps.currentCategory !== this.props.currentCategory) { + if ( + prevProps.currentCategory !== this.props.currentCategory && + this.props.currentCategory !== this.props.previouslySetCategory + ) { + this.props.setCurrentCategory(this.props.currentCategory); this.props.clearProviders(); this.props.fetchProvidersWithCategory({ category: this.props.currentCategory, @@ -427,17 +476,22 @@ class ApiHomeScreen extends React.Component { {ApiHomepageTopSection} {/* Marketplace APIs section start */} -

{"Marketplace APIs"}

- - - +
+
+

{"Marketplace APIs"}

+
+
+ + + +
+
-


@@ -449,51 +503,61 @@ class ApiHomeScreen extends React.Component { <>
- {providers.map(provider => ( - - ( + + history.push( + getProviderTemplatesURL( applicationId, pageId, - provider.id, - destinationPageId - ? destinationPageId - : pageId, + provider.id + + `/?importTo=${destinationPageId}`, ), - state: { - providerName: provider.name, - providerImage: provider.imageUrl, - }, - }} + ) + } + > + - - {provider.imageUrl ? ( - Provider - ) : ( - Provider - )} - {provider.name && ( -

- {provider.name} -

- )} -
- + {provider.imageUrl ? ( + Provider + ) : ( +
+ + { + getInitialsAndColorCode( + provider.name, + )[0] + } + +
+ )} + {provider.name && ( +

+ {provider.name} +

+ )} +
))}
@@ -523,6 +587,13 @@ const mapStateToProps = (state: AppState) => { category = formData.category; } + let initialCategoryValue; + if (state.ui.apiPane.currentCategory === "") { + initialCategoryValue = DEFAULT_PROVIDER_OPTION; + } else { + initialCategoryValue = state.ui.apiPane.currentCategory; + } + return { currentCategory: category, importedCollections: getImportedCollections(state), @@ -533,6 +604,8 @@ const mapStateToProps = (state: AppState) => { providerCategories: getProviderCategories(state), plugins: state.entities.plugins.list, isSwitchingCategory, + previouslySetCategory: state.ui.apiPane.currentCategory, + initialValues: { category: initialCategoryValue }, }; }; @@ -546,13 +619,17 @@ const mapDispatchToProps = (dispatch: any) => ({ createAction: (data: Partial) => dispatch(createActionRequest(data)), createNewApiAction: (pageId: string) => dispatch(createNewApiAction(pageId)), + setCurrentCategory: (category: string) => + dispatch(setCurrentCategory(category)), + setLastUsedEditorPage: (path: string) => + dispatch(setLastUsedEditorPage(path)), }); export default connect( mapStateToProps, mapDispatchToProps, )( - reduxForm({ + reduxForm<{ category: string }, ApiHomeScreenProps>({ form: API_HOME_SCREEN_FORM, })(ApiHomeScreen), ); diff --git a/app/client/src/pages/Editor/APIEditor/Form.tsx b/app/client/src/pages/Editor/APIEditor/Form.tsx index a77f802193..e407f7382a 100644 --- a/app/client/src/pages/Editor/APIEditor/Form.tsx +++ b/app/client/src/pages/Editor/APIEditor/Form.tsx @@ -16,6 +16,7 @@ import FormLabel from "components/editorComponents/FormLabel"; import FormRow from "components/editorComponents/FormRow"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { RestAction, PaginationField } from "api/ActionAPI"; +import { ReduxActionTypes } from "constants/ReduxActionConstants"; import TextField from "components/editorComponents/form/fields/TextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DropdownField from "components/editorComponents/form/fields/DropdownField"; @@ -118,6 +119,10 @@ interface APIFormProps { key: string; value: string; }; + location: { + pathname: string; + }; + dispatch: any; } type Props = APIFormProps & InjectedFormProps; @@ -139,6 +144,8 @@ const ApiEditorForm: React.FC = (props: Props) => { httpMethodFromForm, contentType, displayFormat, + location, + dispatch, } = props; const allowPostBody = httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0]; @@ -153,6 +160,12 @@ const ApiEditorForm: React.FC = (props: Props) => { } } } + dispatch({ + type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE, + payload: { + path: location.pathname, + }, + }); }); return ( diff --git a/app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx b/app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx index 56df2b4694..aafb6dbb22 100644 --- a/app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx +++ b/app/client/src/pages/Editor/APIEditor/ProviderTemplates.tsx @@ -2,7 +2,6 @@ import React from "react"; import { connect } from "react-redux"; import { Icon, Collapse } from "@blueprintjs/core"; import { RouteComponentProps } from "react-router-dom"; -import { ReduxActionTypes } from "constants/ReduxActionConstants"; import styled from "styled-components"; import ReactJson from "react-json-view"; import { AppState } from "reducers"; @@ -20,11 +19,21 @@ import { } from "constants/providerConstants"; import { AddApiToPageRequest } from "api/ProvidersApi"; import ImageAlt from "assets/images/no_image.png"; -import { addApiToPage } from "actions/providerActions"; +import { + setLastUsedEditorPage, + setLastSelectedPage, +} from "actions/apiPaneActions"; +import { + getProviderDetailsByProviderId, + fetchProviderTemplates, + addApiToPage, +} from "actions/providerActions"; import { Colors } from "constants/Colors"; import { getDuplicateName } from "utils/AppsmithUtils"; +import { API_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes"; import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent"; import Spinner from "components/editorComponents/Spinner"; +import { getInitialsAndColorCode } from "utils/AppsmithUtils"; const TEMPLATES_TOP_SECTION_HEIGHT = "125px"; @@ -158,7 +167,7 @@ const TemplateCardRightContent = styled.div` margin: auto; justify-content: center; .dropIcon { - padding-left: 15%; + margin-left: 50px; color: #bcccd9; cursor: pointer; } @@ -189,9 +198,13 @@ const URLContainer = styled.div` type ProviderTemplatesProps = { providerTemplates: ProviderTemplateArray[]; + providerDetails: any; actions: ActionDataState; isFetchingProviderTemplates: boolean; + getProviderDetailsByProviderId: (providerId: string) => void; getProviderTemplates: (providerId: string) => void; + setLastUsedEditorPage: (path: string) => void; + setLastSelectedPage: (selectedPageId: string) => void; addApiToPage: (templateData: AddApiToPageRequest) => void; } & RouteComponentProps; @@ -207,12 +220,27 @@ class ProviderTemplates extends React.Component { }; componentDidMount() { - const { providerId } = this.props.match.params; + const { pageId, providerId } = this.props.match.params; + let destinationPageId = new URLSearchParams(this.props.location.search).get( + "importTo", + ); + if (!destinationPageId) { + destinationPageId = pageId; + } + this.props.getProviderDetailsByProviderId(providerId); this.props.getProviderTemplates(providerId); + this.props.setLastUsedEditorPage(this.props.match.url); + this.props.setLastSelectedPage(destinationPageId); } addApiToPage = (templateData: ProviderTemplateArray) => { - const { destinationPageId } = this.props.match.params; + const { pageId } = this.props.match.params; + let destinationPageId = new URLSearchParams(this.props.location.search).get( + "importTo", + ); + if (!destinationPageId) { + destinationPageId = pageId; + } const pageApiNames = this.props.actions .filter(a => a.config.pageId === destinationPageId) .map(a => a.config.name); @@ -256,18 +284,15 @@ class ProviderTemplates extends React.Component { providerTemplates, history, isFetchingProviderTemplates, + providerDetails, } = this.props; - let providerName; - let providerImage; + const { applicationId, pageId } = this.props.match.params; - if (this.props.location.state) { - providerName = new URLSearchParams(this.props.location.state).get( - "providerName", - ); - - providerImage = new URLSearchParams(this.props.location.state).get( - "providerImage", - ); + let destinationPageId = new URLSearchParams(this.props.location.search).get( + "importTo", + ); + if (destinationPageId === null || destinationPageId === undefined) { + destinationPageId = pageId; } if (isFetchingProviderTemplates) { @@ -294,28 +319,67 @@ class ProviderTemplates extends React.Component { icon="chevron-left" iconSize={16} className="backBtn" - onClick={() => history.goBack()} + onClick={() => + history.push( + API_EDITOR_URL_WITH_SELECTED_PAGE_ID( + applicationId, + pageId, + destinationPageId ? destinationPageId : pageId, + ), + ) + } /> - history.goBack()}> + + history.push( + API_EDITOR_URL_WITH_SELECTED_PAGE_ID( + applicationId, + pageId, + destinationPageId ? destinationPageId : pageId, + ), + ) + } + > {" Back"}
- {providerImage ? ( + {providerDetails.imageUrl ? ( provider ) : ( - provider +
+ {providerDetails.name && ( +
+ + {getInitialsAndColorCode(providerDetails.name)[0]} + +
+ )} +
)} -

{providerName}

+

{providerDetails.name}

@@ -446,16 +510,22 @@ const mapStateToProps = (state: AppState) => ({ providerTemplates: getProviderTemplates(state), isFetchingProviderTemplates: getProvidersTemplatesLoadingState(state), actions: state.entities.actions, + providerDetails: state.ui.providers.providerDetailsByProviderId, }); const mapDispatchToProps = (dispatch: any) => ({ + getProviderDetailsByProviderId: (providerId: string) => + dispatch(getProviderDetailsByProviderId(providerId)), + getProviderTemplates: (providerId: string) => - dispatch({ - type: ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT, - payload: { - providerId, - }, - }), + dispatch(fetchProviderTemplates(providerId)), + + setLastUsedEditorPage: (path: string) => + dispatch(setLastUsedEditorPage(path)), + + setLastSelectedPage: (selectedPageId: string) => + dispatch(setLastSelectedPage(selectedPageId)), + addApiToPage: (templateData: AddApiToPageRequest) => dispatch(addApiToPage(templateData)), }); diff --git a/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx index 169dc5685c..8f3681f1e8 100644 --- a/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { connect } from "react-redux"; import { reduxForm, @@ -17,12 +17,14 @@ import { BodyFormData, Property, } from "api/ActionAPI"; +import { ReduxActionTypes } from "constants/ReduxActionConstants"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DropdownField from "components/editorComponents/form/fields/DropdownField"; import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray"; import ApiResponseView from "components/editorComponents/ApiResponseView"; import { API_EDITOR_FORM_NAME } from "constants/forms"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; +import CredentialsTooltip from "components/editorComponents/form/CredentialsTooltip"; import { FormIcons } from "icons/FormIcons"; import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView"; import Pagination, { PaginationType } from "./Pagination"; @@ -52,13 +54,16 @@ const Form = styled.form` const MainConfiguration = styled.div` padding-top: 10px; padding-left: 17px; + span.bp3-popover-target { + display: inline-block; + } `; const SecondaryWrapper = styled.div` display: flex; height: 100%; border-top: 1px solid #d0d7dd; - margin-top: 15px; + margin-top: 10px; `; const RequestParamsWrapper = styled.div` @@ -120,6 +125,10 @@ interface APIFormProps { providerImage: string; providerURL: string; providerCredentialSteps: string; + location: { + pathname: string; + }; + dispatch: any; } type Props = APIFormProps & InjectedFormProps; @@ -140,6 +149,9 @@ const RapidApiEditorForm: React.FC = (props: Props) => { actionConfigurationBodyFormData, providerImage, providerURL, + providerCredentialSteps, + location, + dispatch, } = props; const postbodyResponsePresent = @@ -147,6 +159,26 @@ const RapidApiEditorForm: React.FC = (props: Props) => { actionConfiguration && actionConfigurationBodyFormData.length > 0; + // let credentialStepsData; + // if (providerCredentialSteps.length !== 0) { + // credentialStepsData = providerCredentialSteps.split("\\n"); + // } + // console.log(credentialStepsData, "credentialStepsData"); + + useEffect(() => { + dispatch({ + type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE, + payload: { + path: location.pathname, + }, + }); + }); + + // const abc = (text: string) => { + // console.log(text, "text"); + + // } + return (
{isSaving && Saving...} @@ -199,6 +231,12 @@ const RapidApiEditorForm: React.FC = (props: Props) => { disabled={true} /> + {/* Display How to get Credentials info if it is present */} + {providerCredentialSteps && providerCredentialSteps !== "" && ( + + )} diff --git a/app/client/src/pages/Editor/APIEditor/index.tsx b/app/client/src/pages/Editor/APIEditor/index.tsx index 7bd4b1bd31..270dc2abb2 100644 --- a/app/client/src/pages/Editor/APIEditor/index.tsx +++ b/app/client/src/pages/Editor/APIEditor/index.tsx @@ -155,6 +155,7 @@ class ApiEditor extends React.Component { pageId={this.props.match.params.pageId} history={this.props.history} location={this.props.location} + match={this.props.match} /> ); const defaultHomeScreen = ( @@ -185,6 +186,7 @@ class ApiEditor extends React.Component { ? this.props.currentApplication.name : "" } + location={this.props.location} /> )} @@ -204,6 +206,7 @@ class ApiEditor extends React.Component { ? this.props.currentApplication.name : "" } + location={this.props.location} /> )} diff --git a/app/client/src/reducers/uiReducers/apiPaneReducer.ts b/app/client/src/reducers/uiReducers/apiPaneReducer.ts index 14eb7005a0..43e71e7f8e 100644 --- a/app/client/src/reducers/uiReducers/apiPaneReducer.ts +++ b/app/client/src/reducers/uiReducers/apiPaneReducer.ts @@ -14,6 +14,9 @@ const initialState: ApiPaneReduxState = { isRunning: {}, isSaving: {}, isDeleting: {}, + currentCategory: "", + lastUsedEditorPage: "", + lastSelectedPage: "", }; export interface ApiPaneReduxState { @@ -23,6 +26,9 @@ export interface ApiPaneReduxState { isRunning: Record; isSaving: Record; isDeleting: Record; + currentCategory: string; + lastUsedEditorPage: string; + lastSelectedPage: string; } const apiPaneReducer = createReducer(initialState, { @@ -159,6 +165,27 @@ const apiPaneReducer = createReducer(initialState, { ...state, lastUsed: "", }), + [ReduxActionTypes.SET_CURRENT_CATEGORY]: ( + state: ApiPaneReduxState, + action: ReduxAction<{ category: string }>, + ) => ({ + ...state, + currentCategory: action.payload.category, + }), + [ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE]: ( + state: ApiPaneReduxState, + action: ReduxAction<{ path: string }>, + ) => ({ + ...state, + lastUsedEditorPage: action.payload.path, + }), + [ReduxActionTypes.SET_LAST_SELECTED_PAGE_PAGE]: ( + state: ApiPaneReduxState, + action: ReduxAction<{ selectedPageId: string }>, + ) => ({ + ...state, + lastSelectedPage: action.payload.selectedPageId, + }), }); export default apiPaneReducer; diff --git a/app/client/src/reducers/uiReducers/providerReducer.ts b/app/client/src/reducers/uiReducers/providerReducer.ts index dc424d1f75..2bc8c5bc87 100644 --- a/app/client/src/reducers/uiReducers/providerReducer.ts +++ b/app/client/src/reducers/uiReducers/providerReducer.ts @@ -11,6 +11,7 @@ import { ProviderTemplates, ProviderTemplateArray, ProvidersCategoriesResponse, + FetchProviderDetailsResponse, } from "constants/providerConstants"; const initialState: ProvidersReduxState = { @@ -22,6 +23,9 @@ const initialState: ProvidersReduxState = { providerCategories: [], providerCategoriesErrorPayload: {}, isSwitchingCategory: true, + lastUsedProviderId: "", + providerDetailsByProviderId: {}, + providerDetailsErrorPayload: {}, }; const providersReducer = createReducer(initialState, { @@ -75,10 +79,14 @@ const providersReducer = createReducer(initialState, { }, [ReduxActionTypes.FETCH_PROVIDER_TEMPLATES_INIT]: ( state: ProvidersReduxState, - ) => ({ - ...state, - isFetchingProviderTemplates: true, - }), + action: any, + ) => { + return { + ...state, + isFetchingProviderTemplates: true, + lastUsedProviderId: action.payload.providerId, + }; + }, [ReduxActionTypes.CLEAR_PROVIDERS]: (state: ProvidersReduxState) => ({ ...state, providers: [], @@ -146,6 +154,21 @@ const providersReducer = createReducer(initialState, { ) => { return { ...state, providerCategoriesErrorPayload: action.payload }; }, + [ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS]: ( + state: ProvidersReduxState, + action: ReduxAction, + ) => { + return { + ...state, + providerDetailsByProviderId: action.payload, + }; + }, + [ReduxActionErrorTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR]: ( + state: ProvidersReduxState, + action: ReduxAction<{ providerDetailsErrorPayload: string }>, + ) => { + return { ...state, providerDetailsErrorPayload: action.payload }; + }, }); export interface ProvidersReduxState { @@ -157,6 +180,9 @@ export interface ProvidersReduxState { providerCategories: string[]; providerCategoriesErrorPayload: {}; isSwitchingCategory: boolean; + lastUsedProviderId: string; + providerDetailsByProviderId: {}; + providerDetailsErrorPayload: {}; } export default providersReducer; diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index 37e051312c..40b3578d5c 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -10,18 +10,23 @@ import { ReduxFormActionTypes, } from "constants/ReduxActionConstants"; import { getFormData } from "selectors/formSelectors"; -import { API_EDITOR_FORM_NAME, API_HOME_SCREEN_FORM } from "constants/forms"; +import { API_EDITOR_FORM_NAME } from "constants/forms"; import { DEFAULT_API_ACTION, POST_BODY_FORMAT_OPTIONS, REST_PLUGIN_PACKAGE_NAME, } from "constants/ApiEditorConstants"; import history from "utils/history"; -import { API_EDITOR_ID_URL, API_EDITOR_URL } from "constants/routes"; +import { + API_EDITOR_ID_URL, + API_EDITOR_URL, + getProviderTemplatesURL, +} from "constants/routes"; import { getCurrentApplicationId, getCurrentPageId, getIsEditorInitialized, + getLastSelectedPage, } from "selectors/editorSelectors"; import { initialize, autofill } from "redux-form"; import { getAction } from "./ActionSagas"; @@ -34,7 +39,6 @@ import { UNIQUE_NAME_ERROR, VALID_FUNCTION_NAME_ERROR, } from "constants/messages"; -import { fetchProvidersWithCategory } from "actions/providerActions"; import { createNewApiName } from "utils/AppsmithUtils"; import { getPluginIdOfPackageName } from "sagas/selectors"; import { getActions } from "selectors/entitiesSelector"; @@ -51,6 +55,10 @@ const getActionConfigs = (state: AppState): ActionData["config"][] => state.entities.actions.map(a => a.config); const getLastUsedAction = (state: AppState) => state.ui.apiPane.lastUsed; +const getLastUsedEditorPage = (state: AppState) => + state.ui.apiPane.lastUsedEditorPage; +const getLastUsedProvider = (state: AppState) => + state.ui.providers.lastUsedProviderId; function* initApiPaneSaga(actionPayload: ReduxAction<{ id?: string }>) { const isInitialized = yield select(getIsEditorInitialized); @@ -59,13 +67,32 @@ function* initApiPaneSaga(actionPayload: ReduxAction<{ id?: string }>) { } const urlId = actionPayload.payload.id; const lastUsedId = yield select(getLastUsedAction); + const lastUsedProviderId = yield select(getLastUsedProvider); + const applicationId = yield select(getCurrentApplicationId); + const pageId = yield select(getCurrentPageId); + const lastUsedEditorPage = yield select(getLastUsedEditorPage); + let lastSelectedPage = yield select(getLastSelectedPage); + if (lastSelectedPage === "") { + lastSelectedPage = pageId; + } + let id = ""; if (urlId) { id = urlId; } else if (lastUsedId) { id = lastUsedId; } - yield put(changeApi(id)); + if (lastUsedProviderId && lastUsedEditorPage.includes("provider")) { + history.push( + getProviderTemplatesURL( + applicationId, + pageId, + lastUsedProviderId + `/?importTo=${lastSelectedPage}`, + ), + ); + } else { + yield put(changeApi(id)); + } } function* syncApiParamsSaga( @@ -324,12 +351,6 @@ function* formValueChangeSaga( actionPayload: ReduxActionWithMeta, ) { const { form } = actionPayload.meta; - if (form === API_HOME_SCREEN_FORM) { - yield put( - fetchProvidersWithCategory({ category: actionPayload.payload, page: 1 }), - ); - return; - } if (form !== API_EDITOR_FORM_NAME) return; yield all([ call(validateInputSaga, actionPayload), diff --git a/app/client/src/sagas/ProvidersSaga.ts b/app/client/src/sagas/ProvidersSaga.ts index 2ee4dc6f04..ce1bf4c43a 100644 --- a/app/client/src/sagas/ProvidersSaga.ts +++ b/app/client/src/sagas/ProvidersSaga.ts @@ -11,6 +11,8 @@ import ProvidersApi, { FetchProviderTemplatesRequest, AddApiToPageRequest, FetchProviderCategoriesResponse, + FetchProviderDetailsByProviderIdRequest, + FetchProviderDetailsResponse, } from "api/ProvidersApi"; import { Providers } from "constants/providerConstants"; import { FetchProviderWithCategoryRequest } from "api/ProvidersApi"; @@ -129,6 +131,35 @@ export function* fetchProvidersCategoriesSaga() { } } +export function* fetchProviderDetailsByProviderIdSaga( + action: ReduxActionWithPromise, +) { + const { providerId } = action.payload; + try { + const request: FetchProviderDetailsByProviderIdRequest = { providerId }; + + const response: FetchProviderDetailsResponse = yield ProvidersApi.fetchProviderDetailsByProviderId( + request, + ); + + const isValidResponse = yield validateResponse(response); + + if (isValidResponse) { + yield put({ + type: ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_SUCCESS, + payload: response.data, + }); + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_ERROR, + payload: { + error, + }, + }); + } +} + export default function* providersSagas() { yield all([ takeLatest( @@ -144,5 +175,9 @@ export default function* providersSagas() { ReduxActionTypes.FETCH_PROVIDERS_WITH_CATEGORY_INIT, fetchProvidersWithCategorySaga, ), + takeLatest( + ReduxActionTypes.FETCH_PROVIDER_DETAILS_BY_PROVIDER_ID_INIT, + fetchProviderDetailsByProviderIdSaga, + ), ]); } diff --git a/app/client/src/selectors/editorSelectors.tsx b/app/client/src/selectors/editorSelectors.tsx index fef7e12f42..d0455af2b5 100644 --- a/app/client/src/selectors/editorSelectors.tsx +++ b/app/client/src/selectors/editorSelectors.tsx @@ -25,6 +25,8 @@ const getWidgetConfigs = (state: AppState) => state.entities.widgetConfig; const getWidgetSideBar = (state: AppState) => state.ui.widgetSidebar; const getPageListState = (state: AppState) => state.entities.pageList; const getActions = (state: AppState) => state.entities.actions; +export const getLastSelectedPage = (state: AppState) => + state.ui.apiPane.lastSelectedPage; export const getProviderCategories = (state: AppState) => state.ui.providers.providerCategories; diff --git a/app/client/src/utils/AppsmithUtils.tsx b/app/client/src/utils/AppsmithUtils.tsx index cea187e63f..0bb804f6c0 100644 --- a/app/client/src/utils/AppsmithUtils.tsx +++ b/app/client/src/utils/AppsmithUtils.tsx @@ -10,6 +10,7 @@ import _ from "lodash"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import * as log from "loglevel"; import { LogLevelDesc } from "loglevel"; +import { providerBackgroundColors } from "constants/providerConstants"; export const createReducer = ( initialState: any, @@ -119,3 +120,32 @@ const getEnvLogLevel = (configLevel: LogLevelDesc): LogLevelDesc => { if (localStorageLevel) logLevel = localStorageLevel; return logLevel; }; + +export const getInitialsAndColorCode = (fullName: any): string[] => { + console.log(fullName, "fullName"); + let inits = ""; + // if name contains space. eg: "Full Name" + if (fullName.includes(" ")) { + const namesArr = fullName.split(" "); + let initials = namesArr.map((name: string) => name.charAt(0)); + initials = initials.join("").toUpperCase(); + inits = initials.slice(0, 2); + } else { + // handle for camelCase + const str = fullName.replace(/([a-z])([A-Z])/g, "$1 $2"); + const namesArr = str.split(" "); + let initials = namesArr.map((name: string) => name.charAt(0)); + initials = initials.join("").toUpperCase(); + inits = initials.slice(0, 2); + } + const colorCode = getColorCode(inits); + return [inits, colorCode]; +}; + +const getColorCode = (initials: string): string => { + let asciiSum = 0; + for (let i = 0; i < initials.length; i++) { + asciiSum += initials[i].charCodeAt(0); + } + return providerBackgroundColors[asciiSum % providerBackgroundColors.length]; +};