diff --git a/app/client/.eslintrc.js b/app/client/.eslintrc.js index 805048b859..4518ef96ce 100644 --- a/app/client/.eslintrc.js +++ b/app/client/.eslintrc.js @@ -16,7 +16,7 @@ module.exports = { }, rules: { "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/no-explicit-any": 0 + "@typescript-eslint/no-explicit-any": 0, }, settings: { react: { diff --git a/app/client/package.json b/app/client/package.json index 3df7d547cb..c8dad3cf4c 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -19,7 +19,6 @@ "@types/lodash": "^4.14.120", "@types/moment-timezone": "^0.5.10", "@types/nanoid": "^2.0.0", - "@types/netlify-identity-widget": "^1.4.1", "@types/node": "^10.12.18", "@types/react": "^16.8.2", "@types/react-dom": "^16.8.0", @@ -27,6 +26,7 @@ "@types/react-redux": "^7.0.1", "@types/react-router-dom": "^5.1.2", "@types/styled-components": "^4.1.8", + "@types/tinycolor2": "^1.4.2", "@uppy/core": "^1.5.1", "@uppy/file-input": "^1.3.1", "@uppy/google-drive": "^1.3.2", @@ -50,7 +50,6 @@ "monaco-editor": "^0.15.1", "monaco-editor-webpack-plugin": "^1.7.0", "nanoid": "^2.0.4", - "netlify-identity-widget": "^1.5.5", "node-sass": "^4.11.0", "normalizr": "^3.3.0", "popper.js": "^1.15.0", @@ -64,13 +63,13 @@ "react-dom": "^16.7.0", "react-helmet": "^5.2.1", "react-monaco-editor": "^0.31.1", - "react-netlify-identity": "^0.1.9", "react-redux": "^6.0.0", "react-rnd": "^10.1.1", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "react-scripts": "^3.1.1", "react-select": "^3.0.8", + "react-transition-group": "^4.3.0", "react-simple-tree-menu": "^1.1.9", "react-tabs": "^3.0.0", "redux": "^4.0.1", @@ -80,6 +79,7 @@ "shallowequal": "^1.1.0", "source-map-explorer": "^2.1.1", "styled-components": "^4.1.3", + "tinycolor2": "^1.4.1", "ts-loader": "^6.0.4", "typescript": "^3.6.3" }, diff --git a/app/client/public/index.html b/app/client/public/index.html index 2b9d087f2d..ec593cba40 100755 --- a/app/client/public/index.html +++ b/app/client/public/index.html @@ -2,7 +2,6 @@ - @@ -11,6 +10,10 @@ + + + + diff --git a/app/client/src/api/ActionAPI.tsx b/app/client/src/api/ActionAPI.tsx index 266e4ff0cc..f04e7522c4 100644 --- a/app/client/src/api/ActionAPI.tsx +++ b/app/client/src/api/ActionAPI.tsx @@ -112,7 +112,7 @@ class ActionAPI extends API { static updateAPI( apiConfig: Partial, ): AxiosPromise { - return API.put(`${ActionAPI.url}/${apiConfig.id}`, null, apiConfig); + return API.put(`${ActionAPI.url}/${apiConfig.id}`, apiConfig); } static deleteAction(id: string) { diff --git a/app/client/src/api/Api.tsx b/app/client/src/api/Api.tsx index a2736a456e..ace2e70327 100644 --- a/app/client/src/api/Api.tsx +++ b/app/client/src/api/Api.tsx @@ -1,21 +1,23 @@ import _ from "lodash"; -import axios from "axios"; +import axios, { AxiosInstance, AxiosRequestConfig } from "axios"; import { getAppsmithConfigs } from "configs"; import { REQUEST_TIMEOUT_MS, - REQUEST_HEADERS, - AUTH_CREDENTIALS, + API_REQUEST_HEADERS, } from "constants/ApiConstants"; import { ActionApiResponse } from "./ActionAPI"; -const { apiUrl } = getAppsmithConfigs(); +import { AUTH_LOGIN_URL } from "constants/routes"; +const { apiUrl, baseUrl } = getAppsmithConfigs(); -const axiosInstance = axios.create({ - baseURL: apiUrl, +//TODO(abhinav): Refactor this to make more composable. +export const apiRequestConfig = { + baseURL: baseUrl + apiUrl, timeout: REQUEST_TIMEOUT_MS, - headers: REQUEST_HEADERS, + headers: API_REQUEST_HEADERS, withCredentials: true, - auth: AUTH_CREDENTIALS, -}); +}; + +const axiosInstance: AxiosInstance = axios.create(); const executeActionRegex = /actions\/execute/; axiosInstance.interceptors.request.use((config: any) => { @@ -45,9 +47,13 @@ axiosInstance.interceptors.response.use( if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx - console.log(error.response.data); - console.log(error.response.status); - console.log(error.response.headers); + // console.log(error.response.data); + // console.log(error.response.status); + // console.log(error.response.headers); + if (error.response.status === 401) { + window.location.href = AUTH_LOGIN_URL; + } + return Promise.reject(error.response.data); } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of @@ -63,29 +69,51 @@ axiosInstance.interceptors.response.use( ); class Api { - static get(url: string, queryParams?: any) { + static get( + url: string, + queryParams?: any, + config?: Partial, + ) { return axiosInstance.get( url + this.convertObjectToQueryParams(queryParams), + _.merge(apiRequestConfig, config), ); } - static post(url: string, body?: any, queryParams?: any) { + static post( + url: string, + body?: any, + queryParams?: any, + config?: Partial, + ) { return axiosInstance.post( url + this.convertObjectToQueryParams(queryParams), body, + _.merge(apiRequestConfig, config), ); } - static put(url: string, queryParams?: any, body?: any) { + static put( + url: string, + body?: any, + queryParams?: any, + config?: Partial, + ) { return axiosInstance.put( url + this.convertObjectToQueryParams(queryParams), body, + _.merge(apiRequestConfig, config), ); } - static delete(url: string, queryParams?: any) { + static delete( + url: string, + queryParams?: any, + config?: Partial, + ) { return axiosInstance.delete( url + this.convertObjectToQueryParams(queryParams), + _.merge(apiRequestConfig, config), ); } diff --git a/app/client/src/api/PageApi.tsx b/app/client/src/api/PageApi.tsx index 3330c7f79f..0715fc5667 100644 --- a/app/client/src/api/PageApi.tsx +++ b/app/client/src/api/PageApi.tsx @@ -89,7 +89,6 @@ class PageApi extends Api { savePageRequest.pageId, savePageRequest.layoutId, ), - undefined, body, ); } diff --git a/app/client/src/api/UserApi.tsx b/app/client/src/api/UserApi.tsx new file mode 100644 index 0000000000..5cd9080f1d --- /dev/null +++ b/app/client/src/api/UserApi.tsx @@ -0,0 +1,67 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ApiResponse } from "./ApiResponses"; + +export interface LoginUserRequest { + email: string; + password: string; +} + +export interface CreateUserRequest { + email: string; + password: string; +} + +export interface CreateUserResponse extends ApiResponse { + email: string; + id: string; +} + +export interface ForgotPasswordRequest { + email: string; +} + +export interface ResetPasswordRequest { + token: string; + user: { + password: string; + email: string; + }; +} + +export interface ResetPasswordVerifyTokenRequest { + email: string; + token: string; +} + +class UserApi extends Api { + static createURL = "v1/users"; + static forgotPasswordURL = "v1/users/forgotPassword"; + static verifyResetPasswordTokenURL = "v1/users/verifyPasswordResetToken"; + static resetPasswordURL = "v1/users/resetPassword"; + static createUser( + request: CreateUserRequest, + ): AxiosPromise { + return Api.post(UserApi.createURL, request); + } + + static forgotPassword( + request: ForgotPasswordRequest, + ): AxiosPromise { + return Api.get(UserApi.forgotPasswordURL, request); + } + + static resetPassword( + request: ResetPasswordRequest, + ): AxiosPromise { + return Api.put(UserApi.resetPasswordURL, request); + } + + static verifyResetPasswordToken( + request: ResetPasswordVerifyTokenRequest, + ): AxiosPromise { + return Api.get(UserApi.verifyResetPasswordTokenURL, request); + } +} + +export default UserApi; diff --git a/app/client/src/assets/images/Github.png b/app/client/src/assets/images/Github.png new file mode 100644 index 0000000000..ea6ff545a2 Binary files /dev/null and b/app/client/src/assets/images/Github.png differ diff --git a/app/client/src/assets/images/Google.png b/app/client/src/assets/images/Google.png new file mode 100644 index 0000000000..f90e063212 Binary files /dev/null and b/app/client/src/assets/images/Google.png differ diff --git a/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx b/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx index c814ff279d..8f807abf30 100644 --- a/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/TextInputComponent.tsx @@ -77,6 +77,7 @@ export interface TextInputProps extends IInputGroupProps { showError?: boolean; /** Additional classname */ className?: string; + type?: string; refHandler?: (ref: HTMLInputElement | null) => void; } diff --git a/app/client/src/components/designSystems/blueprint/Checkbox.tsx b/app/client/src/components/designSystems/blueprint/Checkbox.tsx new file mode 100644 index 0000000000..a4f25f9f39 --- /dev/null +++ b/app/client/src/components/designSystems/blueprint/Checkbox.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import styled from "styled-components"; +import { + Checkbox as BlueprintCheckbox, + ICheckboxProps, +} from "@blueprintjs/core"; +import { + IntentColors, + Intent, + getBorderCSSShorthand, +} from "constants/DefaultTheme"; + +export type CheckboxProps = ICheckboxProps & { + intent: Intent; + align: "left" | "right"; +}; + +export const StyledCheckbox = styled(BlueprintCheckbox)` + &&&& { + span.bp3-control-indicator { + outline: none; + background: white; + box-shadow: none; + border-radius: ${props => props.theme.radii[1]}px; + border: ${props => getBorderCSSShorthand(props.theme.borders[3])}; + height: ${props => props.theme.fontSizes[5]}px; + width: ${props => props.theme.fontSizes[5]}px; + } + input:checked ~ span.bp3-control-indicator { + background: ${props => IntentColors[props.intent]}; + box-shadow: none; + outline: none; + } + } +`; + +export const Checkbox = (props: CheckboxProps) => { + return ; +}; + +export default Checkbox; diff --git a/app/client/src/components/editorComponents/ContextDropdown.tsx b/app/client/src/components/editorComponents/ContextDropdown.tsx index 1429ec6d84..4c9f21f3e8 100644 --- a/app/client/src/components/editorComponents/ContextDropdown.tsx +++ b/app/client/src/components/editorComponents/ContextDropdown.tsx @@ -1,7 +1,7 @@ import React, { ReactNode } from "react"; import styled from "styled-components"; import { ItemRenderer, Select } from "@blueprintjs/select"; -import { Button, MenuItem } from "@blueprintjs/core"; +import { Button, MenuItem, Intent as BlueprintIntent } from "@blueprintjs/core"; import { DropdownOption } from "widgets/DropdownWidget"; import { ControlIconName, ControlIcons } from "icons/ControlIcons"; import { noop } from "utils/AppsmithUtils"; @@ -54,7 +54,7 @@ export const ContextDropdown = (props: ContextDropdownProps) => { onClick={option.onSelect} shouldDismissPopover={true} text={option.label || option.value} - intent={option.intent as Intent} + intent={option.intent as BlueprintIntent} popoverProps={{ minimal: true, hoverCloseDelay: 0, diff --git a/app/client/src/components/editorComponents/Divider.tsx b/app/client/src/components/editorComponents/Divider.tsx new file mode 100644 index 0000000000..990c93a261 --- /dev/null +++ b/app/client/src/components/editorComponents/Divider.tsx @@ -0,0 +1,10 @@ +import { Divider } from "@blueprintjs/core"; +import styled from "styled-components"; + +export const StyledDivider = styled(Divider)` + && { + margin: 0; + } +`; + +export default StyledDivider; diff --git a/app/client/src/components/editorComponents/Form.tsx b/app/client/src/components/editorComponents/Form.tsx new file mode 100644 index 0000000000..8684e81ae2 --- /dev/null +++ b/app/client/src/components/editorComponents/Form.tsx @@ -0,0 +1,8 @@ +import { Form } from "redux-form"; +import styled from "styled-components"; + +const StyledForm = styled(Form)` + width: 100%; +`; + +export default StyledForm; diff --git a/app/client/src/components/editorComponents/FormButton.tsx b/app/client/src/components/editorComponents/FormButton.tsx new file mode 100644 index 0000000000..1f25d91372 --- /dev/null +++ b/app/client/src/components/editorComponents/FormButton.tsx @@ -0,0 +1,28 @@ +import { Button } from "@blueprintjs/core"; +import styled from "styled-components"; +import { Intent, IntentColors } from "constants/DefaultTheme"; +import tinycolor from "tinycolor2"; + +type FormButtonProps = { + intent: Intent; +}; + +export default styled(Button)` + &&& { + font-weight: ${props => props.theme.fontWeights[2]}; + border: none; + flex-grow: 1; + outline: none; + box-shadow: none; + background: ${props => IntentColors[props.intent]}; + &:hover { + background: ${props => + new tinycolor(IntentColors[props.intent]).darken(10).toString()}; + } + &:active { + outline: none; + background: ${props => + new tinycolor(IntentColors[props.intent]).darken(20).toString()}; + } + } +`; diff --git a/app/client/src/components/editorComponents/FormGroup.tsx b/app/client/src/components/editorComponents/FormGroup.tsx new file mode 100644 index 0000000000..913ab8b59e --- /dev/null +++ b/app/client/src/components/editorComponents/FormGroup.tsx @@ -0,0 +1,8 @@ +import styled from "styled-components"; +import { FormGroup } from "@blueprintjs/core"; +const StyledFormGroup = styled(FormGroup)` + & { + width: 100%; + } +`; +export default StyledFormGroup; diff --git a/app/client/src/components/editorComponents/Spinner.tsx b/app/client/src/components/editorComponents/Spinner.tsx new file mode 100644 index 0000000000..0b3d1a1332 --- /dev/null +++ b/app/client/src/components/editorComponents/Spinner.tsx @@ -0,0 +1,3 @@ +import { Spinner } from "@blueprintjs/core"; +//TODO(abhinav): Style this when the designs are available. +export default Spinner; diff --git a/app/client/src/components/editorComponents/form/MessageTag.tsx b/app/client/src/components/editorComponents/form/MessageTag.tsx new file mode 100644 index 0000000000..a204a08069 --- /dev/null +++ b/app/client/src/components/editorComponents/form/MessageTag.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import styled from "styled-components"; +import { + Tag, + Intent as BlueprintIntent, + AnchorButton, + Button, +} from "@blueprintjs/core"; +import { Intent, BlueprintIntentsCSS } from "constants/DefaultTheme"; + +export type MessageAction = { + url?: string; + onClick?: (e: React.SyntheticEvent) => void; + text: string; + intent: Intent; +}; + +const StyledTag = styled(Tag)` + &&& { + padding: ${props => props.theme.spaces[8]}px; + font-size: ${props => props.theme.fontSizes[4]}px; + text-align: center; + margin-bottom: ${props => props.theme.spaces[4]}px; + p { + white-space: normal; + margin: 0; + } + } +`; + +const ActionsContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + & .appsmith-message-action-button { + border: none; + ${BlueprintIntentsCSS} + } +`; + +const ActionButton = (props: MessageAction) => { + if (props.url) { + return ( + + ); + } else if (props.onClick) { + return ( +