User Auth Flow
This commit is contained in:
parent
fb028eb13e
commit
ed2ecadbc4
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<script type="text/javascript" src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
|
||||
<script type="text/javascript" src="./page.min.js"></script>
|
||||
<script type="text/javascript" src="/shims/realms-shim.umd.min.js"></script>
|
||||
|
||||
|
|
@ -11,6 +10,10 @@
|
|||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<link href="https://fonts.googleapis.com/css?family=DM+Sans:400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="../node_modules/normalize.css/normalize.css" rel="stylesheet" />
|
||||
<!-- blueprint-icons.css file must be included alongside blueprint.css! -->
|
||||
<link href="../node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css" rel="stylesheet" />
|
||||
<link href="../node_modules/@blueprintjs/core/lib/css/blueprint.css" rel="stylesheet" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ class ActionAPI extends API {
|
|||
static updateAPI(
|
||||
apiConfig: Partial<RestAction>,
|
||||
): AxiosPromise<ActionCreateUpdateResponse> {
|
||||
return API.put(`${ActionAPI.url}/${apiConfig.id}`, null, apiConfig);
|
||||
return API.put(`${ActionAPI.url}/${apiConfig.id}`, apiConfig);
|
||||
}
|
||||
|
||||
static deleteAction(id: string) {
|
||||
|
|
|
|||
|
|
@ -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<AxiosRequestConfig>,
|
||||
) {
|
||||
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<AxiosRequestConfig>,
|
||||
) {
|
||||
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<AxiosRequestConfig>,
|
||||
) {
|
||||
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<AxiosRequestConfig>,
|
||||
) {
|
||||
return axiosInstance.delete(
|
||||
url + this.convertObjectToQueryParams(queryParams),
|
||||
_.merge(apiRequestConfig, config),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ class PageApi extends Api {
|
|||
savePageRequest.pageId,
|
||||
savePageRequest.layoutId,
|
||||
),
|
||||
undefined,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
67
app/client/src/api/UserApi.tsx
Normal file
67
app/client/src/api/UserApi.tsx
Normal file
|
|
@ -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<CreateUserResponse> {
|
||||
return Api.post(UserApi.createURL, request);
|
||||
}
|
||||
|
||||
static forgotPassword(
|
||||
request: ForgotPasswordRequest,
|
||||
): AxiosPromise<ApiResponse> {
|
||||
return Api.get(UserApi.forgotPasswordURL, request);
|
||||
}
|
||||
|
||||
static resetPassword(
|
||||
request: ResetPasswordRequest,
|
||||
): AxiosPromise<ApiResponse> {
|
||||
return Api.put(UserApi.resetPasswordURL, request);
|
||||
}
|
||||
|
||||
static verifyResetPasswordToken(
|
||||
request: ResetPasswordVerifyTokenRequest,
|
||||
): AxiosPromise<ApiResponse> {
|
||||
return Api.get(UserApi.verifyResetPasswordTokenURL, request);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserApi;
|
||||
BIN
app/client/src/assets/images/Github.png
Normal file
BIN
app/client/src/assets/images/Github.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/client/src/assets/images/Google.png
Normal file
BIN
app/client/src/assets/images/Google.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -77,6 +77,7 @@ export interface TextInputProps extends IInputGroupProps {
|
|||
showError?: boolean;
|
||||
/** Additional classname */
|
||||
className?: string;
|
||||
type?: string;
|
||||
refHandler?: (ref: HTMLInputElement | null) => void;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)<CheckboxProps>`
|
||||
&&&& {
|
||||
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 <StyledCheckbox {...props} alignIndicator={props.align} />;
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
10
app/client/src/components/editorComponents/Divider.tsx
Normal file
10
app/client/src/components/editorComponents/Divider.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Divider } from "@blueprintjs/core";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const StyledDivider = styled(Divider)`
|
||||
&& {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledDivider;
|
||||
8
app/client/src/components/editorComponents/Form.tsx
Normal file
8
app/client/src/components/editorComponents/Form.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Form } from "redux-form";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledForm = styled(Form)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export default StyledForm;
|
||||
28
app/client/src/components/editorComponents/FormButton.tsx
Normal file
28
app/client/src/components/editorComponents/FormButton.tsx
Normal file
|
|
@ -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)<FormButtonProps>`
|
||||
&&& {
|
||||
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()};
|
||||
}
|
||||
}
|
||||
`;
|
||||
8
app/client/src/components/editorComponents/FormGroup.tsx
Normal file
8
app/client/src/components/editorComponents/FormGroup.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import styled from "styled-components";
|
||||
import { FormGroup } from "@blueprintjs/core";
|
||||
const StyledFormGroup = styled(FormGroup)`
|
||||
& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
export default StyledFormGroup;
|
||||
3
app/client/src/components/editorComponents/Spinner.tsx
Normal file
3
app/client/src/components/editorComponents/Spinner.tsx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { Spinner } from "@blueprintjs/core";
|
||||
//TODO(abhinav): Style this when the designs are available.
|
||||
export default Spinner;
|
||||
|
|
@ -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 (
|
||||
<AnchorButton
|
||||
className="appsmith-message-action-button"
|
||||
href={props.url}
|
||||
text={props.text}
|
||||
minimal
|
||||
intent={props.intent as BlueprintIntent}
|
||||
/>
|
||||
);
|
||||
} else if (props.onClick) {
|
||||
return (
|
||||
<Button
|
||||
className="appsmith-message-action-button"
|
||||
onClick={props.onClick}
|
||||
text={props.text}
|
||||
minimal
|
||||
intent={props.intent as BlueprintIntent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export type MessageTagProps = {
|
||||
intent: Intent;
|
||||
message: string;
|
||||
title?: string;
|
||||
actions?: MessageAction[];
|
||||
};
|
||||
|
||||
export const MessageTag = (props: MessageTagProps) => {
|
||||
const actions =
|
||||
props.actions &&
|
||||
props.actions.map(action => <ActionButton key={action.text} {...action} />);
|
||||
return (
|
||||
<StyledTag fill large minimal intent={props.intent as BlueprintIntent}>
|
||||
{props.title && <h4>{props.title}</h4>}
|
||||
<p>{props.message}</p>
|
||||
{actions && <ActionsContainer>{actions}</ActionsContainer>}
|
||||
</StyledTag>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageTag;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
import { Field, BaseFieldProps } from "redux-form";
|
||||
import Checkbox, {
|
||||
CheckboxProps,
|
||||
} from "components/designSystems/blueprint/Checkbox";
|
||||
export const CheckboxField = (props: BaseFieldProps & CheckboxProps) => {
|
||||
return <Field type="checkbox" component={Checkbox} {...props} />;
|
||||
};
|
||||
|
||||
export default CheckboxField;
|
||||
|
|
@ -5,9 +5,21 @@ import {
|
|||
TextInputProps,
|
||||
} from "components/designSystems/appsmith/TextInputComponent";
|
||||
|
||||
class TextField extends React.Component<BaseFieldProps & TextInputProps> {
|
||||
type FieldProps = {
|
||||
type?: string;
|
||||
};
|
||||
|
||||
class TextField extends React.Component<
|
||||
BaseFieldProps & TextInputProps & FieldProps
|
||||
> {
|
||||
render() {
|
||||
return <Field component={BaseTextInput} {...this.props} />;
|
||||
return (
|
||||
<Field
|
||||
type={this.props.type || "text"}
|
||||
component={BaseTextInput}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { SENTRY_STAGE_CONFIG } from "constants/ThirdPartyConstants";
|
||||
import { STAGE_BASE_API_URL } from "constants/ApiConstants";
|
||||
import { STAGE_BASE_URL } from "constants/ApiConstants";
|
||||
import { AppsmithUIConfigs } from "./types";
|
||||
|
||||
const devConfig: AppsmithUIConfigs = {
|
||||
|
|
@ -13,7 +13,8 @@ const devConfig: AppsmithUIConfigs = {
|
|||
segment: {
|
||||
enabled: false,
|
||||
},
|
||||
apiUrl: STAGE_BASE_API_URL,
|
||||
apiUrl: "/api/",
|
||||
baseUrl: STAGE_BASE_URL,
|
||||
};
|
||||
|
||||
export default devConfig;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const getAppsmithConfigs = (): AppsmithUIConfigs => {
|
|||
return devConfig;
|
||||
default:
|
||||
console.log(
|
||||
"Unknow environment set: ",
|
||||
"Unknown environment set: ",
|
||||
process.env.REACT_APP_ENVIRONMENT,
|
||||
);
|
||||
devConfig.apiUrl = "";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
HOTJAR_PROD_HJID,
|
||||
HOTJAR_PROD_HJSV,
|
||||
} from "constants/ThirdPartyConstants";
|
||||
import { PROD_BASE_API_URL } from "constants/ApiConstants";
|
||||
import { PROD_BASE_URL } from "constants/ApiConstants";
|
||||
import { AppsmithUIConfigs } from "./types";
|
||||
|
||||
export const prodConfig: AppsmithUIConfigs = {
|
||||
|
|
@ -21,7 +21,8 @@ export const prodConfig: AppsmithUIConfigs = {
|
|||
segment: {
|
||||
enabled: true,
|
||||
},
|
||||
apiUrl: PROD_BASE_API_URL,
|
||||
apiUrl: "/api/",
|
||||
baseUrl: PROD_BASE_URL,
|
||||
};
|
||||
|
||||
export default prodConfig;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { SENTRY_STAGE_CONFIG } from "constants/ThirdPartyConstants";
|
||||
import { STAGE_BASE_API_URL } from "constants/ApiConstants";
|
||||
import { STAGE_BASE_URL } from "constants/ApiConstants";
|
||||
import { AppsmithUIConfigs } from "./types";
|
||||
|
||||
const stageConfig: AppsmithUIConfigs = {
|
||||
|
|
@ -13,7 +13,8 @@ const stageConfig: AppsmithUIConfigs = {
|
|||
segment: {
|
||||
enabled: false,
|
||||
},
|
||||
apiUrl: STAGE_BASE_API_URL,
|
||||
apiUrl: "/api/",
|
||||
baseUrl: STAGE_BASE_URL,
|
||||
};
|
||||
|
||||
export default stageConfig;
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ export type AppsmithUIConfigs = {
|
|||
enabled: boolean;
|
||||
};
|
||||
apiUrl: string;
|
||||
baseUrl: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,19 +2,25 @@ export type ContentType =
|
|||
| "application/json"
|
||||
| "application/x-www-form-urlencoded";
|
||||
|
||||
export const STAGE_BASE_API_URL = "https://appsmith-test.herokuapp.com/api/";
|
||||
export const PROD_BASE_API_URL = "https://api.appsmith.com/api/";
|
||||
export const STAGE_BASE_URL = "https://release-api.appsmith.com";
|
||||
export const PROD_BASE_URL = "https://api.appsmith.com";
|
||||
|
||||
export const REQUEST_TIMEOUT_MS = 10000;
|
||||
export const REQUEST_HEADERS: APIHeaders = {
|
||||
|
||||
export const API_REQUEST_HEADERS: APIHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
export const AUTH_CREDENTIALS = {
|
||||
username: "api_user",
|
||||
password: "8uA@;&mB:cnvN~{#",
|
||||
export const FORM_REQUEST_HEADERS: APIHeaders = {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
};
|
||||
|
||||
export const OAuthURL = "/oauth2/authorization";
|
||||
export const GoogleOAuthURL = `${OAuthURL}/google`;
|
||||
export const GithubOAuthURL = `${OAuthURL}/github`;
|
||||
|
||||
export const LOGIN_SUBMIT_PATH = "/login";
|
||||
|
||||
export interface APIException {
|
||||
error: number;
|
||||
message: string;
|
||||
|
|
@ -22,6 +28,7 @@ export interface APIException {
|
|||
|
||||
export interface APIHeaders {
|
||||
"Content-Type": ContentType;
|
||||
Accept?: string;
|
||||
}
|
||||
|
||||
export interface APIRequest {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export const Colors: Record<string, string> = {
|
|||
OXFORD_BLUE: "#2E3D49",
|
||||
FRENCH_PASS: "#BBE8FE",
|
||||
CADET_BLUE: "#A3B3BF",
|
||||
JAFFA: "#F2994A",
|
||||
};
|
||||
|
||||
export type Color = typeof Colors[keyof typeof Colors];
|
||||
|
|
|
|||
|
|
@ -13,7 +13,37 @@ const {
|
|||
ThemeProvider,
|
||||
} = styledComponents as styledComponents.ThemedStyledComponentsModule<Theme>;
|
||||
|
||||
export type Intent = "primary" | "danger" | "warning" | "none";
|
||||
export const IntentColors: Record<string, Color> = {
|
||||
primary: Colors.GREEN,
|
||||
success: Colors.PURPLE,
|
||||
secondary: Colors.GEYSER_LIGHT,
|
||||
danger: Colors.RED,
|
||||
none: Colors.GEYSER_LIGHT,
|
||||
warning: Colors.JAFFA,
|
||||
};
|
||||
|
||||
export type Intent = typeof IntentColors[keyof typeof IntentColors];
|
||||
|
||||
export const BlueprintIntentsCSS = css`
|
||||
&.bp3.minimal.bp3-button {
|
||||
color: ${IntentColors.none};
|
||||
}
|
||||
&.bp3.minimal.bp3-intent-primary {
|
||||
color: ${IntentColors.primary};
|
||||
}
|
||||
&.bp3.minimal.bp3-intent-secondary {
|
||||
color: ${IntentColors.secondary};
|
||||
}
|
||||
&.bp3.minimal.bp3-intent-danger {
|
||||
color: ${IntentColors.danger};
|
||||
}
|
||||
&.bp3.minimal.bp3-intent-warning {
|
||||
color: ${IntentColors.warning};
|
||||
}
|
||||
&.bp3.minimal.bp3-intent-success {
|
||||
color: ${IntentColors.success};
|
||||
}
|
||||
`;
|
||||
|
||||
export type ThemeBorder = {
|
||||
thickness: number;
|
||||
|
|
@ -56,6 +86,14 @@ export type Theme = {
|
|||
hoverBG: Color;
|
||||
hoverBGOpacity: number;
|
||||
};
|
||||
authCard: {
|
||||
width: number;
|
||||
borderRadius: number;
|
||||
background: Color;
|
||||
padding: number;
|
||||
dividerSpacing: number;
|
||||
shadow: string;
|
||||
};
|
||||
shadows: string[];
|
||||
widgets: {
|
||||
tableWidget: {
|
||||
|
|
@ -143,6 +181,11 @@ export const theme: Theme = {
|
|||
style: "solid",
|
||||
color: Colors.GEYSER_LIGHT,
|
||||
},
|
||||
{
|
||||
thickness: 1,
|
||||
style: "solid",
|
||||
color: Colors.FRENCH_PASS,
|
||||
},
|
||||
],
|
||||
sidebarWidth: "300px",
|
||||
headerHeight: "50px",
|
||||
|
|
@ -166,6 +209,14 @@ export const theme: Theme = {
|
|||
hoverBG: Colors.BLACK,
|
||||
hoverBGOpacity: 0.5,
|
||||
},
|
||||
authCard: {
|
||||
width: 612,
|
||||
borderRadius: 16,
|
||||
background: Colors.WHITE,
|
||||
padding: 40,
|
||||
dividerSpacing: 32,
|
||||
shadow: "0px 4px 8px rgba(9, 30, 66, 0.25)",
|
||||
},
|
||||
shadows: ["0px 2px 4px rgba(67, 70, 74, 0.14)"],
|
||||
widgets: {
|
||||
tableWidget: {
|
||||
|
|
|
|||
|
|
@ -88,8 +88,19 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
UPDATE_API_DRAFT: "UPDATE_API_DRAFT",
|
||||
DELETE_API_DRAFT: "DELETE_API_DRAFT",
|
||||
UPDATE_ROUTES_PARAMS: "UPDATE_ROUTES_PARAMS",
|
||||
PERSIST_USER_SESSION: "PERSIST_USER_SESSION",
|
||||
LOGIN_USER_INIT: "LOGIN_USER_INIT",
|
||||
LOGIN_USER_SUCCESS: "LOGIN_USER_SUCCESS",
|
||||
CREATE_USER_INIT: "CREATE_USER_INIT",
|
||||
CREATE_USER_SUCCESS: "CREATE_USER_SUCCESS",
|
||||
RESET_USER_PASSWORD_INIT: "RESET_USER_PASSWORD_INIT",
|
||||
RESET_USER_PASSWORD_SUCCESS: "RESET_USER_PASSWORD_SUCCESS",
|
||||
FETCH_PLUGINS_REQUEST: "FETCH_PLUGINS_REQUEST",
|
||||
FETCH_PLUGINS_SUCCESS: "FETCH_PLUGINS_SUCCESS",
|
||||
FORGOT_PASSWORD_INIT: "FORGOT_PASSWORD_INIT",
|
||||
FORGOT_PASSWORD_SUCCESS: "FORGOT_PASSWORD_SUCCESS",
|
||||
RESET_PASSWORD_VERIFY_TOKEN_SUCCESS: "RESET_PASSWORD_VERIFY_TOKEN_SUCCESS",
|
||||
RESET_PASSWORD_VERIFY_TOKEN_INIT: "RESET_PASSWORD_VERIFY_TOKEN_INIT",
|
||||
EXECUTE_PAGE_LOAD_ACTIONS: "EXECUTE_PAGE_LOAD_ACTIONS",
|
||||
};
|
||||
|
||||
|
|
@ -125,8 +136,13 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
|
|||
FETCH_PAGE_LIST_ERROR: "FETCH_PAGE_LIST_ERROR",
|
||||
FETCH_APPLICATION_LIST_ERROR: "FETCH_APPLICATION_LIST_ERROR",
|
||||
CREATE_APPLICATION_ERROR: "CREATE_APPLICATION_ERROR",
|
||||
LOGIN_USER_ERROR: "LOGIN_USER_ERROR",
|
||||
CREATE_USER_ERROR: "CREATE_USER_ERROR",
|
||||
RESET_USER_PASSWORD_ERROR: "RESET_USER_PASSWORD_ERROR",
|
||||
SAVE_JS_EXECUTION_RECORD: "SAVE_JS_EXECUTION_RECORD",
|
||||
FETCH_PLUGINS_ERROR: "FETCH_PLUGINS_ERROR",
|
||||
FORGOT_PASSWORD_ERROR: "FORGOT_PASSWORD_ERROR",
|
||||
RESET_PASSWORD_VERIFY_TOKEN_ERROR: "RESET_PASSWORD_VERIFY_TOKEN_ERROR",
|
||||
};
|
||||
|
||||
export const ReduxFormActionTypes: { [key: string]: string } = {
|
||||
|
|
|
|||
44
app/client/src/constants/SocialLogin.tsx
Normal file
44
app/client/src/constants/SocialLogin.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { GoogleOAuthURL, GithubOAuthURL } from "constants/ApiConstants";
|
||||
import GithubLogo from "assets/images/Github.png";
|
||||
import GoogleLogo from "assets/images/Google.png";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
const { baseUrl } = getAppsmithConfigs();
|
||||
export type SocialLoginButtonProps = {
|
||||
url: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
};
|
||||
|
||||
export const GoogleSocialLoginButtonProps: SocialLoginButtonProps = {
|
||||
url: baseUrl + GoogleOAuthURL,
|
||||
name: "Google",
|
||||
logo: GoogleLogo,
|
||||
};
|
||||
|
||||
export const GithubSocialLoginButtonProps: SocialLoginButtonProps = {
|
||||
url: baseUrl + GithubOAuthURL,
|
||||
name: "Github",
|
||||
logo: GithubLogo,
|
||||
};
|
||||
|
||||
export const SocialLoginButtonPropsList: Record<
|
||||
string,
|
||||
SocialLoginButtonProps
|
||||
> = {
|
||||
google: GoogleSocialLoginButtonProps,
|
||||
github: GithubSocialLoginButtonProps,
|
||||
};
|
||||
|
||||
export type SocialLoginType = keyof typeof SocialLoginButtonPropsList;
|
||||
|
||||
export const getSocialLoginButtonProps = (
|
||||
logins: SocialLoginType[],
|
||||
): SocialLoginButtonProps[] => {
|
||||
return logins.map(login => {
|
||||
const socialLoginButtonProps = SocialLoginButtonPropsList[login];
|
||||
if (!socialLoginButtonProps) {
|
||||
throw Error("Social login not registered: " + login);
|
||||
}
|
||||
return socialLoginButtonProps;
|
||||
});
|
||||
};
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
export const API_EDITOR_FORM_NAME = "ApiEditorForm";
|
||||
export const CREATE_APPLICATION_FORM_NAME = "CreateApplicationForm";
|
||||
export const LOGIN_FORM_NAME = "LoginForm";
|
||||
export const SIGNUP_FORM_NAME = "SignupForm";
|
||||
export const FORGOT_PASSWORD_FORM_NAME = "ForgotPasswordForm";
|
||||
export const RESET_PASSWORD_FORM_NAME = "ResetPasswordForm";
|
||||
|
|
|
|||
|
|
@ -7,4 +7,78 @@ export const FIELD_REQUIRED_ERROR = "This field is required";
|
|||
export const VALID_FUNCTION_NAME_ERROR =
|
||||
"Action name is not a valid function name";
|
||||
export const UNIQUE_NAME_ERROR = "Action name must be unique";
|
||||
|
||||
export const FORM_VALIDATION_EMPTY_EMAIL = "Please enter an email";
|
||||
export const FORM_VALIDATION_INVALID_EMAIL =
|
||||
"Please provide a valid email address";
|
||||
export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password";
|
||||
export const FORM_VALIDATION_INVALID_PASSWORD =
|
||||
"Please provide a password with a minimum of 6 characters";
|
||||
|
||||
export const LOGIN_PAGE_SUBTITLE = "Use your organization email";
|
||||
export const LOGIN_PAGE_TITLE = "Login";
|
||||
export const LOGIN_PAGE_EMAIL_INPUT_LABEL = "Email";
|
||||
export const LOGIN_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
||||
export const LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email";
|
||||
export const LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password";
|
||||
export const LOGIN_PAGE_INVALID_CREDS_ERROR =
|
||||
"Oops! It looks like you may have forgotten your password.";
|
||||
export const LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK = "Reset Password";
|
||||
|
||||
export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "Login";
|
||||
export const LOGIN_PAGE_FORGOT_PASSWORD_TEXT = "Forgot Password";
|
||||
export const LOGIN_PAGE_REMEMBER_ME_LABEL = "Remember";
|
||||
export const LOGIN_PAGE_SIGN_UP_LINK_TEXT = "New to Appsmith? Sign up";
|
||||
export const SIGNUP_PAGE_TITLE = "Sign Up";
|
||||
export const SIGNUP_PAGE_SUBTITLE = "Use your organization email";
|
||||
export const SIGNUP_PAGE_EMAIL_INPUT_LABEL = "Email";
|
||||
export const SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER = " Email";
|
||||
export const SIGNUP_PAGE_NAME_INPUT_PLACEHOLDER = "Name";
|
||||
export const SIGNUP_PAGE_NAME_INPUT_LABEL = "Name";
|
||||
export const SIGNUP_PAGE_PASSWORD_INPUT_LABEL = "Password";
|
||||
export const SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password";
|
||||
export const SIGNUP_PAGE_LOGIN_LINK_TEXT = "Have an account? Login";
|
||||
export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "How should we call you?";
|
||||
export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = "Sign Up";
|
||||
|
||||
export const SIGNUP_PAGE_SUCCESS = "Awesome! You have successfully registered.";
|
||||
export const SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT = "Login";
|
||||
|
||||
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL = "New Password";
|
||||
export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER = "New Password";
|
||||
export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Changed your mind? Login";
|
||||
export const RESET_PASSWORD_PAGE_TITLE = "Reset Password";
|
||||
export const RESET_PASSWORD_SUBMIT_BUTTON_TEXT = "Reset";
|
||||
export const RESET_PASSWORD_PAGE_SUBTITLE =
|
||||
"Create a new password for your account ";
|
||||
|
||||
export const RESET_PASSWORD_RESET_SUCCESS =
|
||||
"Your password has been reset. Please"; //"Your password has been reset. Please login" (see next entry);
|
||||
export const RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK = "login";
|
||||
|
||||
export const RESET_PASSWORD_EXPIRED_TOKEN =
|
||||
"The password reset link has expired. Please try generating a new link";
|
||||
export const RESET_PASSWORD_INVALID_TOKEN =
|
||||
"The password reset link is invalid. Please try generating a new link";
|
||||
export const RESET_PASSWORD_FORGOT_PASSWORD_LINK = "Forgot Password";
|
||||
|
||||
export const FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL = "Email";
|
||||
export const FORGOT_PASSWORD_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email";
|
||||
export const FORGOT_PASSWORD_PAGE_TITLE = "Reset Password";
|
||||
export const FORGOT_PASSWORD_PAGE_SUBTITLE =
|
||||
"We will send a reset link to the email below";
|
||||
export const FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT = "Reset";
|
||||
export const FORGOT_PASSWORD_SUCCESS_TEXT =
|
||||
"A password reset link has been sent to";
|
||||
|
||||
export const PRIVACY_POLICY_LINK = "Privacy Policy";
|
||||
export const TERMS_AND_CONDITIONS_LINK = "Terms and Conditions";
|
||||
|
||||
export const ERROR_500 =
|
||||
"We apologize, Something went wrong. We're working to fix things.";
|
||||
|
||||
export const ERROR_401 =
|
||||
"We are unable to verify your identity. Please login again.";
|
||||
export const ERROR_403 =
|
||||
"Permission Denied. Please contact your admin to gain access.";
|
||||
export const WIDGET_TYPE_VALIDATION_ERROR = "Value does not match type";
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { MenuIcons } from "icons/MenuIcons";
|
||||
export const BASE_URL = "/";
|
||||
export const LOGIN_URL = "/login";
|
||||
export const APPLICATIONS_URL = `/applications`;
|
||||
export const BUILDER_URL = "/applications/:applicationId/pages/:pageId/edit";
|
||||
export const USER_AUTH_URL = "/user";
|
||||
|
||||
export type BuilderRouteParams = {
|
||||
applicationId: string;
|
||||
|
|
@ -67,3 +67,8 @@ export const EDITOR_ROUTES = [
|
|||
exact: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const FORGOT_PASSWORD_URL = `${USER_AUTH_URL}/forgotPassword`;
|
||||
export const RESET_PASSWORD_URL = `${USER_AUTH_URL}/resetPassword`;
|
||||
export const SIGN_UP_URL = `${USER_AUTH_URL}/signup`;
|
||||
export const AUTH_LOGIN_URL = `${USER_AUTH_URL}/login`;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
@import "~normalize.css";
|
||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@ import React, { lazy, Suspense } from "react";
|
|||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import Loader from "pages/common/Loader";
|
||||
import "normalize.css/normalize.css";
|
||||
import "@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
import "@blueprintjs/core/lib/css/blueprint.css";
|
||||
import "./index.css";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { Router, Route, Switch } from "react-router-dom";
|
||||
|
|
@ -24,17 +21,17 @@ import { composeWithDevTools } from "redux-devtools-extension/logOnlyInProductio
|
|||
import {
|
||||
BASE_URL,
|
||||
BUILDER_URL,
|
||||
LOGIN_URL,
|
||||
APP_VIEW_URL,
|
||||
APPLICATIONS_URL,
|
||||
USER_AUTH_URL,
|
||||
} from "./constants/routes";
|
||||
|
||||
const loadingIndicator = <Loader />;
|
||||
const App = lazy(() => import("./App"));
|
||||
const UserAuth = lazy(() => import("./pages/UserAuth"));
|
||||
const Editor = lazy(() => import("./pages/Editor"));
|
||||
const Applications = lazy(() => import("./pages/Applications"));
|
||||
const PageNotFound = lazy(() => import("./pages/common/PageNotFound"));
|
||||
const LoginPage = lazy(() => import("./pages/common/LoginPage"));
|
||||
const AppViewer = lazy(() => import("./pages/AppViewer"));
|
||||
|
||||
appInitializer();
|
||||
|
|
@ -54,6 +51,7 @@ ReactDOM.render(
|
|||
<Suspense fallback={loadingIndicator}>
|
||||
<Switch>
|
||||
<Route exact path={BASE_URL} component={App} />
|
||||
<Route path={USER_AUTH_URL} component={UserAuth} />
|
||||
<ProtectedRoute path={BUILDER_URL} component={Editor} />
|
||||
<ProtectedRoute path={APP_VIEW_URL} component={AppViewer} />
|
||||
<ProtectedRoute
|
||||
|
|
@ -61,7 +59,6 @@ ReactDOM.render(
|
|||
path={APPLICATIONS_URL}
|
||||
component={Applications}
|
||||
/>
|
||||
<Route exact path={LOGIN_URL} component={LoginPage} />
|
||||
<Route component={PageNotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ import { CREATE_APPLICATION_FORM_NAME } from "constants/forms";
|
|||
import {
|
||||
CreateApplicationFormValues,
|
||||
createApplicationFormSubmitHandler,
|
||||
} from "utils/formhelpers";
|
||||
} from "./helpers";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import { required } from "utils/validation/common";
|
||||
import { FormGroup } from "@blueprintjs/core";
|
||||
|
||||
export const CreateApplicationForm = (
|
||||
|
|
@ -15,12 +14,8 @@ export const CreateApplicationForm = (
|
|||
const { error, handleSubmit } = props;
|
||||
return (
|
||||
<Form onSubmit={handleSubmit(createApplicationFormSubmitHandler)}>
|
||||
<FormGroup intent={error ? "danger" : "none"} helperText={error}>
|
||||
<TextField
|
||||
name="applicationName"
|
||||
placeholder="Name"
|
||||
validate={required}
|
||||
/>
|
||||
<FormGroup intent={error ? "danger" : "none"}>
|
||||
<TextField name="applicationName" placeholder="Name" />
|
||||
</FormGroup>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
24
app/client/src/pages/Applications/helpers.ts
Normal file
24
app/client/src/pages/Applications/helpers.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { SubmissionError } from "redux-form";
|
||||
export type CreateApplicationFormValues = {
|
||||
applicationName: string;
|
||||
};
|
||||
|
||||
export const createApplicationFormSubmitHandler = (
|
||||
values: CreateApplicationFormValues,
|
||||
dispatch: any,
|
||||
): Promise<any> => {
|
||||
const { applicationName } = values;
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.CREATE_APPLICATION_INIT,
|
||||
payload: {
|
||||
resolve,
|
||||
reject,
|
||||
applicationName,
|
||||
},
|
||||
});
|
||||
}).catch(error => {
|
||||
throw new SubmissionError(error);
|
||||
});
|
||||
};
|
||||
119
app/client/src/pages/UserAuth/ForgotPassword.tsx
Normal file
119
app/client/src/pages/UserAuth/ForgotPassword.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form";
|
||||
import StyledForm from "components/editorComponents/Form";
|
||||
import {
|
||||
AuthCardContainer,
|
||||
AuthCardHeader,
|
||||
AuthCardBody,
|
||||
FormActions,
|
||||
} from "./StyledComponents";
|
||||
import {
|
||||
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL,
|
||||
FORGOT_PASSWORD_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||
FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT,
|
||||
FORGOT_PASSWORD_PAGE_SUBTITLE,
|
||||
FORGOT_PASSWORD_PAGE_TITLE,
|
||||
FORM_VALIDATION_EMPTY_EMAIL,
|
||||
FORM_VALIDATION_INVALID_EMAIL,
|
||||
FORGOT_PASSWORD_SUCCESS_TEXT,
|
||||
} from "constants/messages";
|
||||
|
||||
import MessageTag from "components/editorComponents/form/MessageTag";
|
||||
|
||||
import { FORGOT_PASSWORD_FORM_NAME } from "constants/forms";
|
||||
import FormGroup from "components/editorComponents/FormGroup";
|
||||
import FormButton from "components/editorComponents/FormButton";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import { isEmail, isEmptyString } from "utils/formhelpers";
|
||||
import {
|
||||
ForgotPasswordFormValues,
|
||||
forgotPasswordSubmitHandler,
|
||||
} from "./helpers";
|
||||
|
||||
const validate = (values: ForgotPasswordFormValues) => {
|
||||
const errors: ForgotPasswordFormValues = {};
|
||||
if (!values.email || isEmptyString(values.email)) {
|
||||
errors.email = FORM_VALIDATION_EMPTY_EMAIL;
|
||||
} else if (!isEmail(values.email)) {
|
||||
errors.email = FORM_VALIDATION_INVALID_EMAIL;
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
type ForgotPasswordProps = InjectedFormProps<
|
||||
ForgotPasswordFormValues,
|
||||
{ emailValue: string }
|
||||
> &
|
||||
RouteComponentProps<{ email: string }> & { emailValue: string };
|
||||
|
||||
export const ForgotPassword = (props: ForgotPasswordProps) => {
|
||||
const {
|
||||
error,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
submitFailed,
|
||||
submitSucceeded,
|
||||
} = props;
|
||||
const queryParams = new URLSearchParams(props.location.search);
|
||||
const hasEmail = queryParams.get("email");
|
||||
return (
|
||||
<AuthCardContainer>
|
||||
{submitSucceeded && (
|
||||
<MessageTag
|
||||
intent="success"
|
||||
message={`${FORGOT_PASSWORD_SUCCESS_TEXT} ${props.emailValue}`}
|
||||
/>
|
||||
)}
|
||||
{submitFailed && error && <MessageTag intent="danger" message={error} />}
|
||||
<AuthCardHeader>
|
||||
<h1>{FORGOT_PASSWORD_PAGE_TITLE}</h1>
|
||||
<h5>{FORGOT_PASSWORD_PAGE_SUBTITLE}</h5>
|
||||
</AuthCardHeader>
|
||||
<AuthCardBody>
|
||||
<StyledForm onSubmit={handleSubmit(forgotPasswordSubmitHandler)}>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
name="email"
|
||||
placeholder={FORGOT_PASSWORD_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
disabled={submitting}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormActions>
|
||||
<FormButton
|
||||
type="submit"
|
||||
text={FORGOT_PASSWORD_PAGE_SUBMIT_BUTTON_TEXT}
|
||||
intent="primary"
|
||||
disabled={pristine && !hasEmail}
|
||||
loading={submitting}
|
||||
/>
|
||||
</FormActions>
|
||||
</StyledForm>
|
||||
</AuthCardBody>
|
||||
</AuthCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = formValueSelector(FORGOT_PASSWORD_FORM_NAME);
|
||||
|
||||
export default connect((state, props: ForgotPasswordProps) => {
|
||||
const queryParams = new URLSearchParams(props.location.search);
|
||||
return {
|
||||
initialValues: {
|
||||
email: queryParams.get("email") || "",
|
||||
},
|
||||
emailValue: selector(state, "email"),
|
||||
};
|
||||
})(
|
||||
reduxForm<ForgotPasswordFormValues, { emailValue: string }>({
|
||||
validate,
|
||||
form: FORGOT_PASSWORD_FORM_NAME,
|
||||
touchOnBlur: true,
|
||||
})(withRouter(ForgotPassword)),
|
||||
);
|
||||
162
app/client/src/pages/UserAuth/Login.tsx
Normal file
162
app/client/src/pages/UserAuth/Login.tsx
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import React from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { InjectedFormProps, reduxForm, formValueSelector } from "redux-form";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { LOGIN_FORM_NAME } from "constants/forms";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
import { FORGOT_PASSWORD_URL, SIGN_UP_URL } from "constants/routes";
|
||||
import { LOGIN_SUBMIT_PATH } from "constants/ApiConstants";
|
||||
import {
|
||||
LOGIN_PAGE_SUBTITLE,
|
||||
LOGIN_PAGE_TITLE,
|
||||
LOGIN_PAGE_EMAIL_INPUT_LABEL,
|
||||
LOGIN_PAGE_PASSWORD_INPUT_LABEL,
|
||||
LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||
LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||
FORM_VALIDATION_EMPTY_EMAIL,
|
||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||
FORM_VALIDATION_INVALID_EMAIL,
|
||||
FORM_VALIDATION_INVALID_PASSWORD,
|
||||
LOGIN_PAGE_LOGIN_BUTTON_TEXT,
|
||||
LOGIN_PAGE_FORGOT_PASSWORD_TEXT,
|
||||
LOGIN_PAGE_SIGN_UP_LINK_TEXT,
|
||||
LOGIN_PAGE_INVALID_CREDS_ERROR,
|
||||
PRIVACY_POLICY_LINK,
|
||||
TERMS_AND_CONDITIONS_LINK,
|
||||
LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK,
|
||||
} from "constants/messages";
|
||||
import Divider from "components/editorComponents/Divider";
|
||||
import MessageTag from "components/editorComponents/form/MessageTag";
|
||||
import FormGroup from "components/editorComponents/FormGroup";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import FormButton from "components/editorComponents/FormButton";
|
||||
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
||||
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
||||
import { LoginFormValues } from "./helpers";
|
||||
|
||||
import {
|
||||
AuthCardContainer,
|
||||
SpacedSubmitForm,
|
||||
FormActions,
|
||||
AuthCardHeader,
|
||||
AuthCardFooter,
|
||||
AuthCardNavLink,
|
||||
AuthCardBody,
|
||||
} from "./StyledComponents";
|
||||
|
||||
const validate = (values: LoginFormValues) => {
|
||||
const errors: LoginFormValues = {};
|
||||
if (!values.password || isEmptyString(values.password)) {
|
||||
errors.password = FORM_VALIDATION_EMPTY_PASSWORD;
|
||||
} else if (!isStrongPassword(values.password)) {
|
||||
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
|
||||
}
|
||||
if (!values.username || isEmptyString(values.username)) {
|
||||
errors.username = FORM_VALIDATION_EMPTY_EMAIL;
|
||||
} else if (!isEmail(values.username)) {
|
||||
errors.username = FORM_VALIDATION_INVALID_EMAIL;
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
type LoginFormProps = { emailValue: string } & InjectedFormProps<
|
||||
LoginFormValues,
|
||||
{ emailValue: string }
|
||||
>;
|
||||
|
||||
export const Login = (props: LoginFormProps) => {
|
||||
const { error, pristine } = props;
|
||||
const location = useLocation();
|
||||
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
let showError = false;
|
||||
if (queryParams.get("error")) {
|
||||
showError = true;
|
||||
}
|
||||
|
||||
let forgotPasswordURL = `${FORGOT_PASSWORD_URL}`;
|
||||
if (props.emailValue && !isEmptyString(props.emailValue)) {
|
||||
forgotPasswordURL += `?email=${props.emailValue}`;
|
||||
}
|
||||
|
||||
const { baseUrl } = getAppsmithConfigs();
|
||||
|
||||
return (
|
||||
<AuthCardContainer>
|
||||
{showError && pristine && (
|
||||
<MessageTag
|
||||
intent="danger"
|
||||
message={LOGIN_PAGE_INVALID_CREDS_ERROR}
|
||||
actions={[
|
||||
{
|
||||
url: FORGOT_PASSWORD_URL,
|
||||
text: LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK,
|
||||
intent: "success",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<AuthCardHeader>
|
||||
<h1>{LOGIN_PAGE_TITLE}</h1>
|
||||
<h5>{LOGIN_PAGE_SUBTITLE}</h5>
|
||||
</AuthCardHeader>
|
||||
<AuthCardBody>
|
||||
<SpacedSubmitForm method="POST" action={baseUrl + LOGIN_SUBMIT_PATH}>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={LOGIN_PAGE_EMAIL_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
name="username"
|
||||
type="email"
|
||||
placeholder={LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={LOGIN_PAGE_PASSWORD_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder={LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
/>
|
||||
</FormGroup>
|
||||
<Link to={forgotPasswordURL}>{LOGIN_PAGE_FORGOT_PASSWORD_TEXT}</Link>
|
||||
<FormActions>
|
||||
<FormButton
|
||||
type="submit"
|
||||
text={LOGIN_PAGE_LOGIN_BUTTON_TEXT}
|
||||
intent="primary"
|
||||
/>
|
||||
</FormActions>
|
||||
</SpacedSubmitForm>
|
||||
<Divider />
|
||||
<ThirdPartyAuth
|
||||
logins={[SocialLoginTypes.GOOGLE, SocialLoginTypes.GITHUB]}
|
||||
/>
|
||||
</AuthCardBody>
|
||||
<AuthCardNavLink to={SIGN_UP_URL}>
|
||||
{LOGIN_PAGE_SIGN_UP_LINK_TEXT}
|
||||
<Icon icon="arrow-right" intent="primary" />
|
||||
</AuthCardNavLink>
|
||||
<AuthCardFooter>
|
||||
<Link to="#">{PRIVACY_POLICY_LINK}</Link>
|
||||
<Link to="#">{TERMS_AND_CONDITIONS_LINK}</Link>
|
||||
</AuthCardFooter>
|
||||
</AuthCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const selector = formValueSelector(LOGIN_FORM_NAME);
|
||||
export default connect(state => ({
|
||||
emailValue: selector(state, "email"),
|
||||
}))(
|
||||
reduxForm<LoginFormValues, { emailValue: string }>({
|
||||
validate,
|
||||
form: LOGIN_FORM_NAME,
|
||||
})(Login),
|
||||
);
|
||||
224
app/client/src/pages/UserAuth/ResetPassword.tsx
Normal file
224
app/client/src/pages/UserAuth/ResetPassword.tsx
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
import React, { useLayoutEffect } from "react";
|
||||
import { AppState } from "reducers";
|
||||
import { Link, withRouter, RouteComponentProps } from "react-router-dom";
|
||||
import { connect } from "react-redux";
|
||||
import { InjectedFormProps, reduxForm, Field } from "redux-form";
|
||||
import { RESET_PASSWORD_FORM_NAME } from "constants/forms";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { getIsTokenValid, getIsValidatingToken } from "selectors/authSelectors";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import MessageTag, {
|
||||
MessageTagProps,
|
||||
MessageAction,
|
||||
} from "components/editorComponents/form/MessageTag";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
import FormButton from "components/editorComponents/FormButton";
|
||||
import FormGroup from "components/editorComponents/FormGroup";
|
||||
import StyledForm from "components/editorComponents/Form";
|
||||
import { isEmptyString, isStrongPassword } from "utils/formhelpers";
|
||||
|
||||
import { ResetPasswordFormValues, resetPasswordSubmitHandler } from "./helpers";
|
||||
import {
|
||||
AuthCardHeader,
|
||||
AuthCardFooter,
|
||||
AuthCardContainer,
|
||||
AuthCardBody,
|
||||
AuthCardNavLink,
|
||||
FormActions,
|
||||
} from "./StyledComponents";
|
||||
import { AUTH_LOGIN_URL, FORGOT_PASSWORD_URL } from "constants/routes";
|
||||
|
||||
import {
|
||||
RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL,
|
||||
RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||
RESET_PASSWORD_LOGIN_LINK_TEXT,
|
||||
RESET_PASSWORD_SUBMIT_BUTTON_TEXT,
|
||||
RESET_PASSWORD_PAGE_SUBTITLE,
|
||||
RESET_PASSWORD_PAGE_TITLE,
|
||||
FORM_VALIDATION_INVALID_PASSWORD,
|
||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||
RESET_PASSWORD_EXPIRED_TOKEN,
|
||||
RESET_PASSWORD_FORGOT_PASSWORD_LINK,
|
||||
RESET_PASSWORD_INVALID_TOKEN,
|
||||
RESET_PASSWORD_RESET_SUCCESS,
|
||||
RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
|
||||
PRIVACY_POLICY_LINK,
|
||||
TERMS_AND_CONDITIONS_LINK,
|
||||
} from "constants/messages";
|
||||
|
||||
const validate = (values: ResetPasswordFormValues) => {
|
||||
const errors: ResetPasswordFormValues = {};
|
||||
if (!values.password || isEmptyString(values.password)) {
|
||||
errors.password = FORM_VALIDATION_EMPTY_PASSWORD;
|
||||
} else if (!isStrongPassword(values.password)) {
|
||||
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
type ResetPasswordProps = InjectedFormProps<
|
||||
ResetPasswordFormValues,
|
||||
{
|
||||
verifyToken: (token: string, email: string) => void;
|
||||
isTokenValid: boolean;
|
||||
validatingToken: boolean;
|
||||
}
|
||||
> & {
|
||||
verifyToken: (token: string, email: string) => void;
|
||||
isTokenValid: boolean;
|
||||
validatingToken: boolean;
|
||||
} & RouteComponentProps<{ email: string; token: string }>;
|
||||
|
||||
export const ResetPassword = (props: ResetPasswordProps) => {
|
||||
const {
|
||||
error,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
submitSucceeded,
|
||||
submitFailed,
|
||||
initialValues,
|
||||
isTokenValid,
|
||||
validatingToken,
|
||||
verifyToken,
|
||||
} = props;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (initialValues.token && initialValues.email)
|
||||
verifyToken(initialValues.token, initialValues.email);
|
||||
}, [initialValues.token, initialValues.email, verifyToken]);
|
||||
|
||||
const showInvalidMessage = !initialValues.token || !initialValues.email;
|
||||
const showExpiredMessage = !isTokenValid && !validatingToken;
|
||||
const showSuccessMessage = submitSucceeded && !pristine;
|
||||
const showFailureMessage = submitFailed && !!error;
|
||||
|
||||
let message = "";
|
||||
let messageActions: MessageAction[] | undefined = undefined;
|
||||
if (showExpiredMessage || showInvalidMessage) {
|
||||
messageActions = [
|
||||
{
|
||||
url: FORGOT_PASSWORD_URL,
|
||||
text: RESET_PASSWORD_FORGOT_PASSWORD_LINK,
|
||||
intent: "success",
|
||||
},
|
||||
];
|
||||
}
|
||||
if (showExpiredMessage) {
|
||||
message = RESET_PASSWORD_EXPIRED_TOKEN;
|
||||
}
|
||||
if (showInvalidMessage) {
|
||||
message = RESET_PASSWORD_INVALID_TOKEN;
|
||||
}
|
||||
|
||||
if (showSuccessMessage) {
|
||||
message = RESET_PASSWORD_RESET_SUCCESS;
|
||||
messageActions = [
|
||||
{
|
||||
url: AUTH_LOGIN_URL,
|
||||
text: RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK,
|
||||
intent: "success",
|
||||
},
|
||||
];
|
||||
}
|
||||
if (showFailureMessage) {
|
||||
message = error;
|
||||
}
|
||||
|
||||
const messageTagProps: MessageTagProps = {
|
||||
intent:
|
||||
showInvalidMessage || showExpiredMessage || showFailureMessage
|
||||
? "danger"
|
||||
: "success",
|
||||
message,
|
||||
actions: messageActions,
|
||||
};
|
||||
|
||||
if (showInvalidMessage || showExpiredMessage) {
|
||||
return <MessageTag {...messageTagProps} />;
|
||||
}
|
||||
|
||||
if (!isTokenValid && validatingToken) {
|
||||
return <Spinner />;
|
||||
}
|
||||
return (
|
||||
<AuthCardContainer>
|
||||
{(showSuccessMessage || showFailureMessage) && (
|
||||
<MessageTag {...messageTagProps} />
|
||||
)}
|
||||
<AuthCardHeader>
|
||||
<h1>{RESET_PASSWORD_PAGE_TITLE}</h1>
|
||||
<h5>{RESET_PASSWORD_PAGE_SUBTITLE}</h5>
|
||||
</AuthCardHeader>
|
||||
<AuthCardBody>
|
||||
<StyledForm onSubmit={handleSubmit(resetPasswordSubmitHandler)}>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder={RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
/>
|
||||
</FormGroup>
|
||||
<Field type="hidden" name="email" component="input" />
|
||||
<Field type="hidden" name="token" component="input" />
|
||||
<FormActions>
|
||||
<FormButton
|
||||
type="submit"
|
||||
text={RESET_PASSWORD_SUBMIT_BUTTON_TEXT}
|
||||
intent="primary"
|
||||
disabled={pristine}
|
||||
loading={submitting}
|
||||
/>
|
||||
</FormActions>
|
||||
</StyledForm>
|
||||
</AuthCardBody>
|
||||
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
||||
{RESET_PASSWORD_LOGIN_LINK_TEXT}
|
||||
<Icon icon="arrow-right" intent="primary" />
|
||||
</AuthCardNavLink>
|
||||
<AuthCardFooter>
|
||||
<Link to="#">{PRIVACY_POLICY_LINK}</Link>
|
||||
<Link to="#">{TERMS_AND_CONDITIONS_LINK}</Link>
|
||||
</AuthCardFooter>
|
||||
</AuthCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
(state: AppState, props: ResetPasswordProps) => {
|
||||
const queryParams = new URLSearchParams(props.location.search);
|
||||
return {
|
||||
initialValues: {
|
||||
email: queryParams.get("email") || undefined,
|
||||
token: queryParams.get("token") || undefined,
|
||||
},
|
||||
isTokenValid: getIsTokenValid(state),
|
||||
validatingToken: getIsValidatingToken(state),
|
||||
};
|
||||
},
|
||||
(dispatch: any) => ({
|
||||
verifyToken: (token: string, email: string) =>
|
||||
dispatch({
|
||||
type: ReduxActionTypes.RESET_PASSWORD_VERIFY_TOKEN_INIT,
|
||||
payload: { token, email },
|
||||
}),
|
||||
}),
|
||||
)(
|
||||
reduxForm<
|
||||
ResetPasswordFormValues,
|
||||
{
|
||||
verifyToken: (token: string, email: string) => void;
|
||||
validatingToken: boolean;
|
||||
isTokenValid: boolean;
|
||||
}
|
||||
>({
|
||||
validate,
|
||||
form: RESET_PASSWORD_FORM_NAME,
|
||||
touchOnBlur: true,
|
||||
})(withRouter(ResetPassword)),
|
||||
);
|
||||
145
app/client/src/pages/UserAuth/SignUp.tsx
Normal file
145
app/client/src/pages/UserAuth/SignUp.tsx
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import React from "react";
|
||||
import { reduxForm, InjectedFormProps } from "redux-form";
|
||||
import { AUTH_LOGIN_URL } from "constants/routes";
|
||||
import { SIGNUP_FORM_NAME } from "constants/forms";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import Divider from "components/editorComponents/Divider";
|
||||
import {
|
||||
AuthCardHeader,
|
||||
AuthCardBody,
|
||||
AuthCardFooter,
|
||||
AuthCardNavLink,
|
||||
SpacedForm,
|
||||
FormActions,
|
||||
AuthCardContainer,
|
||||
} from "./StyledComponents";
|
||||
import {
|
||||
SIGNUP_PAGE_TITLE,
|
||||
SIGNUP_PAGE_SUBTITLE,
|
||||
SIGNUP_PAGE_EMAIL_INPUT_LABEL,
|
||||
SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER,
|
||||
SIGNUP_PAGE_PASSWORD_INPUT_LABEL,
|
||||
SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER,
|
||||
SIGNUP_PAGE_LOGIN_LINK_TEXT,
|
||||
FORM_VALIDATION_EMPTY_EMAIL,
|
||||
FORM_VALIDATION_EMPTY_PASSWORD,
|
||||
FORM_VALIDATION_INVALID_EMAIL,
|
||||
FORM_VALIDATION_INVALID_PASSWORD,
|
||||
SIGNUP_PAGE_SUBMIT_BUTTON_TEXT,
|
||||
PRIVACY_POLICY_LINK,
|
||||
TERMS_AND_CONDITIONS_LINK,
|
||||
SIGNUP_PAGE_SUCCESS,
|
||||
SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT,
|
||||
} from "constants/messages";
|
||||
import MessageTag from "components/editorComponents/form/MessageTag";
|
||||
import FormGroup from "components/editorComponents/FormGroup";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import ThirdPartyAuth, { SocialLoginTypes } from "./ThirdPartyAuth";
|
||||
import FormButton from "components/editorComponents/FormButton";
|
||||
|
||||
import { isEmail, isStrongPassword, isEmptyString } from "utils/formhelpers";
|
||||
|
||||
import { signupFormSubmitHandler, SignupFormValues } from "./helpers";
|
||||
|
||||
const validate = (values: SignupFormValues) => {
|
||||
const errors: SignupFormValues = {};
|
||||
if (!values.password || isEmptyString(values.password)) {
|
||||
errors.password = FORM_VALIDATION_EMPTY_PASSWORD;
|
||||
} else if (!isStrongPassword(values.password)) {
|
||||
errors.password = FORM_VALIDATION_INVALID_PASSWORD;
|
||||
}
|
||||
if (!values.email || isEmptyString(values.email)) {
|
||||
errors.email = FORM_VALIDATION_EMPTY_EMAIL;
|
||||
} else if (!isEmail(values.email)) {
|
||||
errors.email = FORM_VALIDATION_INVALID_EMAIL;
|
||||
}
|
||||
return errors;
|
||||
};
|
||||
|
||||
export const SignUp = (props: InjectedFormProps<SignupFormValues>) => {
|
||||
const {
|
||||
error,
|
||||
handleSubmit,
|
||||
submitting,
|
||||
submitFailed,
|
||||
submitSucceeded,
|
||||
pristine,
|
||||
} = props;
|
||||
return (
|
||||
<AuthCardContainer>
|
||||
{submitSucceeded && (
|
||||
<MessageTag
|
||||
intent="success"
|
||||
message={SIGNUP_PAGE_SUCCESS}
|
||||
actions={[
|
||||
{
|
||||
url: AUTH_LOGIN_URL,
|
||||
text: SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT,
|
||||
intent: "success",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{submitFailed && error && <MessageTag intent="danger" message={error} />}
|
||||
<AuthCardHeader>
|
||||
<h1>{SIGNUP_PAGE_TITLE}</h1>
|
||||
<h5>{SIGNUP_PAGE_SUBTITLE}</h5>
|
||||
</AuthCardHeader>
|
||||
<AuthCardBody>
|
||||
<SpacedForm onSubmit={handleSubmit(signupFormSubmitHandler)}>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={SIGNUP_PAGE_EMAIL_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder={SIGNUP_PAGE_EMAIL_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
intent={error ? "danger" : "none"}
|
||||
label={SIGNUP_PAGE_PASSWORD_INPUT_LABEL}
|
||||
>
|
||||
<TextField
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder={SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER}
|
||||
showError
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormActions>
|
||||
<FormButton
|
||||
type="submit"
|
||||
disabled={pristine}
|
||||
loading={submitting}
|
||||
text={SIGNUP_PAGE_SUBMIT_BUTTON_TEXT}
|
||||
intent="primary"
|
||||
/>
|
||||
</FormActions>
|
||||
</SpacedForm>
|
||||
<Divider />
|
||||
<ThirdPartyAuth
|
||||
logins={[SocialLoginTypes.GOOGLE, SocialLoginTypes.GITHUB]}
|
||||
/>
|
||||
</AuthCardBody>
|
||||
|
||||
<AuthCardNavLink to={AUTH_LOGIN_URL}>
|
||||
{SIGNUP_PAGE_LOGIN_LINK_TEXT}
|
||||
<Icon icon="arrow-right" intent="primary" />
|
||||
</AuthCardNavLink>
|
||||
<AuthCardFooter>
|
||||
<Link to="#">{PRIVACY_POLICY_LINK}</Link>
|
||||
<Link to="#">{TERMS_AND_CONDITIONS_LINK}</Link>
|
||||
</AuthCardFooter>
|
||||
</AuthCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default reduxForm<SignupFormValues>({
|
||||
validate,
|
||||
form: SIGNUP_FORM_NAME,
|
||||
touchOnBlur: true,
|
||||
})(SignUp);
|
||||
110
app/client/src/pages/UserAuth/StyledComponents.tsx
Normal file
110
app/client/src/pages/UserAuth/StyledComponents.tsx
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
import styled, { css } from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import Form from "components/editorComponents/Form";
|
||||
import { Card } from "@blueprintjs/core";
|
||||
|
||||
export const AuthContainer = styled.section`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
&& .fade {
|
||||
position: relative;
|
||||
}
|
||||
&& .fade-enter {
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&& .fade-enter.fade-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 250ms ease-in;
|
||||
}
|
||||
.fade-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
.fade-exit-active {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthCard = styled(Card)`
|
||||
width: ${props => props.theme.authCard.width}px;
|
||||
background: ${props => props.theme.authCard.background};
|
||||
border-radius: ${props => props.theme.authCard.borderRadius}px;
|
||||
padding: ${props => props.theme.authCard.padding}px;
|
||||
box-shadow: ${props => props.theme.authCard.shadow};
|
||||
position: relative;
|
||||
border: none;
|
||||
& h1,
|
||||
h5 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-weight: ${props => props.theme.fontWeights[1]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthCardContainer = styled.div``;
|
||||
|
||||
export const AuthCardHeader = styled.header`
|
||||
& {
|
||||
h1 {
|
||||
font-size: ${props => props.theme.fontSizes[6]}px;
|
||||
}
|
||||
h5 {
|
||||
font-size: ${props => props.theme.fontSizes[4]}px;
|
||||
}
|
||||
margin-bottom: ${props => props.theme.authCard.dividerSpacing}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthCardNavLink = styled(Link)`
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
margin-top: ${props => props.theme.spaces[6]}px;
|
||||
& span {
|
||||
margin-left: ${props => props.theme.spaces[4]}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const AuthCardFooter = styled.footer`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-evenly;
|
||||
align-items: baseline;
|
||||
margin-top: ${props => props.theme.authCard.dividerSpacing}px;
|
||||
`;
|
||||
|
||||
export const AuthCardBody = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
& a {
|
||||
margin-top: ${props => props.theme.spaces[8]}px;
|
||||
font-size: ${props => props.theme.fontSizes[2]}px;
|
||||
}
|
||||
`;
|
||||
|
||||
const formSpacing = css`
|
||||
flex-grow: 1;
|
||||
margin-right: ${props => props.theme.authCard.dividerSpacing}px;
|
||||
`;
|
||||
|
||||
export const SpacedForm = styled(Form)`
|
||||
${formSpacing}
|
||||
`;
|
||||
|
||||
export const SpacedSubmitForm = styled.form`
|
||||
${formSpacing}
|
||||
`;
|
||||
|
||||
export const FormActions = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-top: ${props => props.theme.spaces[2]}px;
|
||||
& > label {
|
||||
margin-right: ${props => props.theme.spaces[11]}px;
|
||||
}
|
||||
`;
|
||||
87
app/client/src/pages/UserAuth/ThirdPartyAuth.tsx
Normal file
87
app/client/src/pages/UserAuth/ThirdPartyAuth.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
getSocialLoginButtonProps,
|
||||
SocialLoginType,
|
||||
} from "constants/SocialLogin";
|
||||
import { IntentColors, getBorderCSSShorthand } from "constants/DefaultTheme";
|
||||
|
||||
const ThirdPartyAuthWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-end;
|
||||
margin-left: ${props => props.theme.authCard.dividerSpacing}px;
|
||||
`;
|
||||
|
||||
//TODO(abhinav): Port this to use themes.
|
||||
const StyledSocialLoginButton = styled.a`
|
||||
width: 200px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: ${props => getBorderCSSShorthand(props.theme.borders[2])};
|
||||
padding: 8px;
|
||||
color: ${props => props.theme.colors.textDefault};
|
||||
border-radius: ${props => props.theme.radii[1]}px;
|
||||
position: relative;
|
||||
height: 42px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
background: ${IntentColors.success};
|
||||
color: ${props => props.theme.colors.textOnDarkBG};
|
||||
}
|
||||
& > div {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: ${props => props.theme.radii[1]}px;
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 2px;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
& img {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
}
|
||||
}
|
||||
& p {
|
||||
display: block;
|
||||
margin: 0 0 0 36px;
|
||||
font-size: ${props => props.theme.fontSizes[3]}px;
|
||||
font-weight: ${props => props.theme.fontWeights[3]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SocialLoginTypes: Record<string, string> = {
|
||||
GOOGLE: "google",
|
||||
GITHUB: "github",
|
||||
};
|
||||
|
||||
const SocialLoginButton = (props: {
|
||||
logo: string;
|
||||
name: string;
|
||||
url: string;
|
||||
}) => {
|
||||
return (
|
||||
<StyledSocialLoginButton href={props.url}>
|
||||
<div>
|
||||
<img alt={` ${props.name} login`} src={props.logo} />
|
||||
</div>
|
||||
<p>{`Sign in with ${props.name}`}</p>
|
||||
</StyledSocialLoginButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThirdPartyAuth = (props: { logins: SocialLoginType[] }) => {
|
||||
const socialLoginButtons = getSocialLoginButtonProps(props.logins).map(
|
||||
item => {
|
||||
return <SocialLoginButton key={item.name} {...item}></SocialLoginButton>;
|
||||
},
|
||||
);
|
||||
return <ThirdPartyAuthWrapper>{socialLoginButtons}</ThirdPartyAuthWrapper>;
|
||||
};
|
||||
|
||||
export default ThirdPartyAuth;
|
||||
85
app/client/src/pages/UserAuth/helpers.ts
Normal file
85
app/client/src/pages/UserAuth/helpers.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { SubmissionError } from "redux-form";
|
||||
|
||||
export type LoginFormValues = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
remember?: string;
|
||||
};
|
||||
|
||||
export type SignupFormValues = {
|
||||
email?: string;
|
||||
password?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type ResetPasswordFormValues = {
|
||||
password?: string;
|
||||
token?: string;
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export type ForgotPasswordFormValues = {
|
||||
email?: string;
|
||||
};
|
||||
|
||||
export const signupFormSubmitHandler = (
|
||||
values: SignupFormValues,
|
||||
dispatch: any,
|
||||
): Promise<any> => {
|
||||
const { email, password } = values;
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.CREATE_USER_INIT,
|
||||
payload: {
|
||||
resolve,
|
||||
reject,
|
||||
email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
}).catch(error => {
|
||||
throw new SubmissionError(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const resetPasswordSubmitHandler = (
|
||||
values: ResetPasswordFormValues,
|
||||
dispatch: any,
|
||||
): Promise<any> => {
|
||||
const { token, email, password } = values;
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.RESET_USER_PASSWORD_INIT,
|
||||
payload: {
|
||||
resolve,
|
||||
reject,
|
||||
token,
|
||||
email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
}).catch(error => {
|
||||
throw new SubmissionError(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const forgotPasswordSubmitHandler = (
|
||||
values: ForgotPasswordFormValues,
|
||||
dispatch: any,
|
||||
): Promise<any> => {
|
||||
const { email } = values;
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.FORGOT_PASSWORD_INIT,
|
||||
payload: {
|
||||
resolve,
|
||||
reject,
|
||||
email,
|
||||
},
|
||||
});
|
||||
}).catch(error => {
|
||||
error.email = "";
|
||||
throw new SubmissionError(error);
|
||||
});
|
||||
};
|
||||
43
app/client/src/pages/UserAuth/index.tsx
Normal file
43
app/client/src/pages/UserAuth/index.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
import { Switch, Route, useRouteMatch, useLocation } from "react-router-dom";
|
||||
import { TransitionGroup, CSSTransition } from "react-transition-group";
|
||||
import Login from "./Login";
|
||||
import Centered from "components/designSystems/appsmith/CenteredWrapper";
|
||||
|
||||
import { AuthContainer, AuthCard } from "./StyledComponents";
|
||||
import SignUp from "./SignUp";
|
||||
import ForgotPassword from "./ForgotPassword";
|
||||
import ResetPassword from "./ResetPassword";
|
||||
|
||||
export const UserAuth = () => {
|
||||
const { path } = useRouteMatch();
|
||||
const location = useLocation();
|
||||
return (
|
||||
<AuthContainer>
|
||||
<Centered>
|
||||
<AuthCard>
|
||||
<TransitionGroup>
|
||||
<CSSTransition key={location.key} classNames="fade" timeout={300}>
|
||||
<Switch location={location}>
|
||||
<Route exact path={`${path}/login`} component={Login} />
|
||||
<Route exact path={`${path}/signup`} component={SignUp} />
|
||||
<Route
|
||||
exact
|
||||
path={`${path}/resetPassword`}
|
||||
component={ResetPassword}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path={`${path}/forgotPassword`}
|
||||
component={ForgotPassword}
|
||||
/>
|
||||
</Switch>
|
||||
</CSSTransition>
|
||||
</TransitionGroup>
|
||||
</AuthCard>
|
||||
</Centered>
|
||||
</AuthContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserAuth;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { RouterProps } from "react-router";
|
||||
import netlifyIdentity from "netlify-identity-widget";
|
||||
|
||||
class LoginPage extends React.PureComponent<RouterProps> {
|
||||
componentDidMount() {
|
||||
netlifyIdentity.open();
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ textAlign: "center" }}></div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default LoginPage;
|
||||
|
|
@ -1,8 +1,5 @@
|
|||
import * as React from "react";
|
||||
import _ from "lodash";
|
||||
import { Route, Redirect } from "react-router-dom";
|
||||
|
||||
import netlifyIdentity from "netlify-identity-widget";
|
||||
import { Route } from "react-router-dom";
|
||||
|
||||
const ProtectedRoute = ({
|
||||
component: Component,
|
||||
|
|
@ -12,17 +9,7 @@ const ProtectedRoute = ({
|
|||
component: React.ReactType;
|
||||
exact?: boolean;
|
||||
}) => {
|
||||
const shouldShowLogin =
|
||||
!_.isNil(netlifyIdentity.currentUser()) ||
|
||||
process.env.REACT_APP_TESTING === "TESTING";
|
||||
return (
|
||||
<Route
|
||||
{...rest}
|
||||
render={props =>
|
||||
shouldShowLogin ? <Component {...props} /> : <Redirect to={"/login"} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <Route {...rest} render={props => <Component {...props} />} />;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { PageListReduxState } from "./entityReducers/pageListReducer";
|
|||
import { ApiPaneReduxState } from "./uiReducers/apiPaneReducer";
|
||||
import { RoutesParamsReducerState } from "reducers/uiReducers/routesParamsReducer";
|
||||
import { PluginDataState } from "reducers/entityReducers/pluginsReducer";
|
||||
import { AuthState } from "reducers/uiReducers/authReducer";
|
||||
|
||||
const appReducer = combineReducers({
|
||||
entities: entityReducer,
|
||||
|
|
@ -39,6 +40,7 @@ export interface AppState {
|
|||
applications: ApplicationsReduxState;
|
||||
apiPane: ApiPaneReduxState;
|
||||
routesParams: RoutesParamsReducerState;
|
||||
auth: AuthState;
|
||||
};
|
||||
entities: {
|
||||
canvasWidgets: CanvasWidgetsReduxState;
|
||||
|
|
|
|||
32
app/client/src/reducers/uiReducers/authReducer.ts
Normal file
32
app/client/src/reducers/uiReducers/authReducer.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
const initialState: AuthState = {
|
||||
isValidatingToken: true,
|
||||
isTokenValid: false,
|
||||
};
|
||||
|
||||
const authReducer = createReducer(initialState, {
|
||||
[ReduxActionTypes.RESET_PASSWORD_VERIFY_TOKEN_INIT]: () => ({
|
||||
isTokenValid: false,
|
||||
isValidatingToken: true,
|
||||
}),
|
||||
[ReduxActionTypes.RESET_PASSWORD_VERIFY_TOKEN_SUCCESS]: () => ({
|
||||
isValidatingToken: false,
|
||||
isTokenValid: true,
|
||||
}),
|
||||
[ReduxActionErrorTypes.RESET_PASSWORD_VERIFY_TOKEN_ERROR]: () => ({
|
||||
isValidatingToken: false,
|
||||
isTokenValid: false,
|
||||
}),
|
||||
});
|
||||
|
||||
export interface AuthState {
|
||||
isValidatingToken: boolean;
|
||||
isTokenValid: boolean;
|
||||
}
|
||||
|
||||
export default authReducer;
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
import { createReducer } from "utils/AppsmithUtils";
|
||||
import {
|
||||
ReduxAction,
|
||||
UpdateCanvasPayload,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { ContainerWidgetProps } from "widgets/ContainerWidget";
|
||||
import moment from "moment";
|
||||
import {
|
||||
ReduxAction,
|
||||
UpdateCanvasPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
|
||||
const initialState: EditorReduxState = {
|
||||
loadingStates: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import applicationsReducer from "./applicationsReducer";
|
|||
import { widgetSidebarReducer } from "./widgetSidebarReducer";
|
||||
import apiPaneReducer from "./apiPaneReducer";
|
||||
import routesParamsReducer from "reducers/uiReducers/routesParamsReducer";
|
||||
import authReducer from "./authReducer";
|
||||
|
||||
const uiReducer = combineReducers({
|
||||
widgetSidebar: widgetSidebarReducer,
|
||||
|
|
@ -17,5 +18,6 @@ const uiReducer = combineReducers({
|
|||
applications: applicationsReducer,
|
||||
apiPane: apiPaneReducer,
|
||||
routesParams: routesParamsReducer,
|
||||
auth: authReducer,
|
||||
});
|
||||
export default uiReducer;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,27 @@ import {
|
|||
import AppToaster from "components/editorComponents/ToastComponent";
|
||||
import { DEFAULT_ERROR_MESSAGE, DEFAULT_ACTION_ERROR } from "constants/errors";
|
||||
import { ApiResponse } from "api/ApiResponses";
|
||||
import { put, takeLatest } from "redux-saga/effects";
|
||||
import { put, takeLatest, call } from "redux-saga/effects";
|
||||
import { ERROR_500 } from "constants/messages";
|
||||
|
||||
export function* validateResponse(response: ApiResponse) {
|
||||
export function* callAPI(apiCall: any, requestPayload: any) {
|
||||
try {
|
||||
return yield call(apiCall, requestPayload);
|
||||
} catch (error) {
|
||||
return yield error;
|
||||
}
|
||||
}
|
||||
const getErrorMessage = (code: number) => {
|
||||
switch (code) {
|
||||
case 500:
|
||||
return ERROR_500;
|
||||
}
|
||||
};
|
||||
|
||||
export function* validateResponse(response: ApiResponse | any) {
|
||||
if (!response.responseMeta && response.status) {
|
||||
throw Error(getErrorMessage(response.status));
|
||||
}
|
||||
if (response.responseMeta.success) {
|
||||
return true;
|
||||
} else {
|
||||
|
|
@ -24,6 +42,12 @@ export function* validateResponse(response: ApiResponse) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getResponseErrorMessage(response: ApiResponse) {
|
||||
return response.responseMeta.error
|
||||
? response.responseMeta.error.message
|
||||
: undefined;
|
||||
}
|
||||
|
||||
type ErrorPayloadType = object | { message: string };
|
||||
let ActionErrorDisplayMap: {
|
||||
[key: string]: (error: ErrorPayloadType) => string;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import watchActionWidgetMapSagas, {
|
|||
watchPropertyAndBindingUpdate,
|
||||
} from "./ActionWidgetMapSagas";
|
||||
import apiPaneSagas from "./ApiPaneSagas";
|
||||
import userSagas from "./userSagas";
|
||||
import pluginSagas from "./PluginSagas";
|
||||
|
||||
export function* rootSaga() {
|
||||
|
|
@ -30,6 +31,7 @@ export function* rootSaga() {
|
|||
spawn(watchActionWidgetMapSagas),
|
||||
spawn(watchPropertyAndBindingUpdate),
|
||||
spawn(apiPaneSagas),
|
||||
spawn(userSagas),
|
||||
spawn(pluginSagas),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
168
app/client/src/sagas/userSagas.tsx
Normal file
168
app/client/src/sagas/userSagas.tsx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { call, takeLatest, put, all } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionErrorTypes,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import UserApi, {
|
||||
CreateUserRequest,
|
||||
CreateUserResponse,
|
||||
ForgotPasswordRequest,
|
||||
ResetPasswordRequest,
|
||||
ResetPasswordVerifyTokenRequest,
|
||||
} from "api/UserApi";
|
||||
import { ApiResponse } from "api/ApiResponses";
|
||||
import {
|
||||
validateResponse,
|
||||
getResponseErrorMessage,
|
||||
callAPI,
|
||||
} from "./ErrorSagas";
|
||||
|
||||
export function* createUserSaga(
|
||||
action: ReduxAction<{
|
||||
resolve: any;
|
||||
reject: any;
|
||||
email: string;
|
||||
password: string;
|
||||
}>,
|
||||
) {
|
||||
const { email, password, resolve, reject } = action.payload;
|
||||
try {
|
||||
const request: CreateUserRequest = { email, password };
|
||||
const response: CreateUserResponse = yield callAPI(
|
||||
UserApi.createUser,
|
||||
request,
|
||||
);
|
||||
//TODO(abhinav): DRY this
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (!isValidResponse) {
|
||||
const errorMessage = getResponseErrorMessage(response);
|
||||
yield call(reject, { _error: errorMessage });
|
||||
} else {
|
||||
const { email, name, id } = response.data;
|
||||
yield put({
|
||||
type: ReduxActionTypes.CREATE_USER_SUCCESS,
|
||||
payload: {
|
||||
email,
|
||||
name,
|
||||
id,
|
||||
},
|
||||
});
|
||||
yield call(resolve);
|
||||
}
|
||||
} catch (error) {
|
||||
yield call(reject, { _error: error.message });
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.CREATE_USER_ERROR,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* forgotPasswordSaga(
|
||||
action: ReduxAction<{ resolve: any; reject: any; email: string }>,
|
||||
) {
|
||||
const { email, resolve, reject } = action.payload;
|
||||
|
||||
try {
|
||||
const request: ForgotPasswordRequest = { email };
|
||||
const response: ApiResponse = yield callAPI(
|
||||
UserApi.forgotPassword,
|
||||
request,
|
||||
);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (!isValidResponse) {
|
||||
const errorMessage = yield getResponseErrorMessage(response);
|
||||
yield call(reject, { _error: errorMessage });
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.FORGOT_PASSWORD_SUCCESS,
|
||||
});
|
||||
yield call(resolve);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
yield call(reject, { _error: error.message });
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.FORGOT_PASSWORD_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* resetPasswordSaga(
|
||||
action: ReduxAction<{
|
||||
resolve: any;
|
||||
reject: any;
|
||||
email: string;
|
||||
token: string;
|
||||
password: string;
|
||||
}>,
|
||||
) {
|
||||
const { email, token, password, resolve, reject } = action.payload;
|
||||
try {
|
||||
const request: ResetPasswordRequest = {
|
||||
user: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
token,
|
||||
};
|
||||
const response: ApiResponse = yield callAPI(UserApi.resetPassword, request);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (!isValidResponse) {
|
||||
const errorMessage = yield getResponseErrorMessage(response);
|
||||
yield call(reject, { _error: errorMessage });
|
||||
} else {
|
||||
yield put({
|
||||
type: ReduxActionTypes.RESET_USER_PASSWORD_SUCCESS,
|
||||
});
|
||||
yield call(resolve);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
yield call(reject, { _error: error.message });
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.RESET_USER_PASSWORD_ERROR,
|
||||
payload: {
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* verifyResetPasswordTokenSaga(
|
||||
action: ReduxAction<{ token: string; email: string }>,
|
||||
) {
|
||||
try {
|
||||
const request: ResetPasswordVerifyTokenRequest = action.payload;
|
||||
const response: ApiResponse = yield callAPI(
|
||||
UserApi.verifyResetPasswordToken,
|
||||
request,
|
||||
);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
yield put({
|
||||
type: ReduxActionTypes.RESET_PASSWORD_VERIFY_TOKEN_SUCCESS,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
yield put({
|
||||
type: ReduxActionErrorTypes.RESET_PASSWORD_VERIFY_TOKEN_ERROR,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function* userSagas() {
|
||||
yield all([
|
||||
takeLatest(ReduxActionTypes.CREATE_USER_INIT, createUserSaga),
|
||||
takeLatest(ReduxActionTypes.FORGOT_PASSWORD_INIT, forgotPasswordSaga),
|
||||
takeLatest(ReduxActionTypes.RESET_USER_PASSWORD_INIT, resetPasswordSaga),
|
||||
takeLatest(
|
||||
ReduxActionTypes.RESET_PASSWORD_VERIFY_TOKEN_INIT,
|
||||
verifyResetPasswordTokenSaga,
|
||||
),
|
||||
]);
|
||||
}
|
||||
5
app/client/src/selectors/authSelectors.tsx
Normal file
5
app/client/src/selectors/authSelectors.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { AppState } from "reducers";
|
||||
|
||||
export const getIsTokenValid = (state: AppState) => state.ui.auth.isTokenValid;
|
||||
export const getIsValidatingToken = (state: AppState) =>
|
||||
state.ui.auth.isValidatingToken;
|
||||
|
|
@ -2,7 +2,6 @@ import { ReduxAction } from "../constants/ReduxActionConstants";
|
|||
import { getAppsmithConfigs } from "configs";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import AnalyticsUtil from "./AnalyticsUtil";
|
||||
import netlifyIdentity from "netlify-identity-widget";
|
||||
import FontFaceObserver from "fontfaceobserver";
|
||||
import PropertyControlRegistry from "./PropertyControlRegistry";
|
||||
import WidgetBuilderRegistry from "./WidgetRegistry";
|
||||
|
|
@ -29,7 +28,6 @@ export const appInitializer = () => {
|
|||
WidgetBuilderRegistry.registerWidgetBuilders();
|
||||
PropertyControlRegistry.registerPropertyControlBuilders();
|
||||
ValidationRegistry.registerInternalValidators();
|
||||
netlifyIdentity.init();
|
||||
moment.tz.setDefault(moment.tz.guess());
|
||||
const appsmithConfigs = getAppsmithConfigs();
|
||||
if (appsmithConfigs.sentry.enabled && appsmithConfigs.sentry.config) {
|
||||
|
|
@ -83,4 +81,6 @@ export const getNextWidgetName = (
|
|||
return prefix + (lastIndex + 1);
|
||||
};
|
||||
|
||||
export const noop = () => {};
|
||||
export const noop = () => {
|
||||
console.log("noop");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,25 +1,19 @@
|
|||
import { SubmissionError } from "redux-form";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
const PASSWORD_MIN_LENGTH = 6;
|
||||
|
||||
export type CreateApplicationFormValues = {
|
||||
applicationName: string;
|
||||
export const hashPassword = (password: string) => {
|
||||
return password;
|
||||
};
|
||||
|
||||
export const createApplicationFormSubmitHandler = (
|
||||
values: CreateApplicationFormValues,
|
||||
dispatch: any,
|
||||
): Promise<any> => {
|
||||
const { applicationName } = values;
|
||||
return new Promise((resolve, reject) => {
|
||||
dispatch({
|
||||
type: ReduxActionTypes.CREATE_APPLICATION_INIT,
|
||||
payload: {
|
||||
resolve,
|
||||
reject,
|
||||
applicationName,
|
||||
},
|
||||
});
|
||||
}).catch(error => {
|
||||
throw new SubmissionError(error);
|
||||
});
|
||||
export const isEmptyString = (value: string) => {
|
||||
return !value || value.trim().length === 0 || typeof value !== "string";
|
||||
};
|
||||
|
||||
export const isStrongPassword = (value: string) => {
|
||||
return value.trim().length >= PASSWORD_MIN_LENGTH;
|
||||
};
|
||||
|
||||
// TODO (abhinav): Use a regex which adheres to standards RFC5322
|
||||
export const isEmail = (value: string) => {
|
||||
const re = /^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+.([A-Za-z]{2,5})$/;
|
||||
return re.test(value);
|
||||
};
|
||||
|
|
|
|||
2241
app/client/yarn.lock
2241
app/client/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user