Skip to content

Commit

Permalink
Feature: Add the option for setting installation URL (#126)
Browse files Browse the repository at this point in the history
* Add action/reducer for url settings

* Add screen for configuring the installation url

* Add navigation helper controlling the navigation from outside of component

* Add url helper for setting the base url in API call

* Add configure url in router

* Add some locale texts

* Add helper for URL validation

* Add change url option in login screen

* Fix some prop warnings

* Navigate to configure url screen on logout

* Remove hard coded values

* Release android/ios beta version 0.0.24

* Add brightness for thumbnail background color

* Release android/ios beta version 0.0.25
  • Loading branch information
muhsin-k committed Apr 15, 2020
1 parent af93780 commit 5d24737
Show file tree
Hide file tree
Showing 20 changed files with 370 additions and 42 deletions.
1 change: 1 addition & 0 deletions android/.ruby-version
@@ -0,0 +1 @@
2.6.5
8 changes: 4 additions & 4 deletions android/app/build.gradle
Expand Up @@ -90,12 +90,12 @@ apply from: "../../node_modules/react-native/react.gradle"
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
def enableSeparateBuildPerCPUArchitecture = true

/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
def enableProguardInReleaseBuilds = true

/**
* The preferred build flavor of JavaScriptCore.
Expand Down Expand Up @@ -131,8 +131,8 @@ android {
applicationId "com.chatwoot.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 23
versionName "0.0.23"
versionCode 25
versionName "0.0.25"
}
splits {
abi {
Expand Down
8 changes: 4 additions & 4 deletions ios/Chatwoot.xcodeproj/project.pbxproj
Expand Up @@ -665,12 +665,12 @@
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 24;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = 6C953F3RX2;
INFOPLIST_FILE = Chatwoot/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.0.23;
MARKETING_VERSION = 0.0.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
Expand All @@ -691,11 +691,11 @@
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 23;
CURRENT_PROJECT_VERSION = 24;
DEVELOPMENT_TEAM = 6C953F3RX2;
INFOPLIST_FILE = Chatwoot/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 0.0.23;
MARKETING_VERSION = 0.0.24;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "@chatwoot/mobile-app",
"version": "0.0.23",
"version": "0.0.25",
"private": true,
"scripts": {
"clean": "rm -rf $TMPDIR/react-* && watchman watch-del-all && npm cache clean --force",
Expand Down
11 changes: 7 additions & 4 deletions src/actions/auth.js
@@ -1,5 +1,7 @@
import axios from '../helpers/APIHelper';

import * as RootNavigation from '../helpers/NavigationHelper';

import {
LOGIN,
LOGIN_ERROR,
Expand All @@ -14,7 +16,7 @@ import {
import { showToast } from '../helpers/ToastHelper';
import I18n from '../i18n';

export const onLogin = ({ email, password }) => async dispatch => {
export const onLogin = ({ email, password }) => async (dispatch) => {
try {
dispatch({ type: LOGIN });
const response = await axios.post('auth/sign_in', { email, password });
Expand All @@ -31,7 +33,7 @@ export const onLogin = ({ email, password }) => async dispatch => {
}
};

export const onResetPassword = ({ email }) => async dispatch => {
export const onResetPassword = ({ email }) => async (dispatch) => {
try {
dispatch({ type: RESET_PASSWORD });
const response = await axios.post('auth/password', { email });
Expand All @@ -43,10 +45,11 @@ export const onResetPassword = ({ email }) => async dispatch => {
}
};

export const resetAuth = () => async dispatch => {
export const resetAuth = () => async (dispatch) => {
RootNavigation.navigate('ConfigureURL');
dispatch({ type: RESET_AUTH });
};

export const onLogOut = () => async dispatch => {
export const onLogOut = () => async (dispatch) => {
dispatch({ type: USER_LOGOUT });
};
21 changes: 21 additions & 0 deletions src/actions/settings.js
@@ -0,0 +1,21 @@
import {
SET_URL,
SET_URL_ERROR,
SET_URL_SUCCESS,
RESET_SETTINGS,
} from '../constants/actions';
import * as RootNavigation from '../helpers/NavigationHelper';

export const setInstallationUrl = ({ url }) => async (dispatch) => {
try {
dispatch({ type: SET_URL });
dispatch({ type: SET_URL_SUCCESS, payload: `https://${url}/` });
RootNavigation.navigate('Login');
} catch (error) {
dispatch({ type: SET_URL_ERROR, payload: error });
}
};

export const resetSettings = () => async (dispatch) => {
dispatch({ type: RESET_SETTINGS });
};
6 changes: 5 additions & 1 deletion src/constants/actions.js
@@ -1,5 +1,9 @@
export const SET_LOCALE = 'SET_LOCALE';

export const SET_URL = 'SET_URL';
export const SET_URL_SUCCESS = 'SET_URL_SUCCESS';
export const SET_URL_ERROR = 'SET_URL_ERROR';

export const LOGIN = 'LOGIN';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_ERROR = 'LOGIN_ERROR';
Expand Down Expand Up @@ -50,5 +54,5 @@ export const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS';
export const RESET_PASSWORD_ERROR = 'RESET_PASSWORD_ERROR';

export const RESET_AUTH = 'RESET_AUTH';

export const RESET_SETTINGS = 'RESET_SETTINGS';
export const USER_LOGOUT = 'USER_LOGOUT';
13 changes: 7 additions & 6 deletions src/helpers/APIHelper.js
Expand Up @@ -8,8 +8,9 @@ import { getHeaders } from './AuthHelper';

import { store } from '../store';
import { onLogOut } from '../actions/auth';
import { getBaseUrl } from './UrlHelper';

const parseErrorCode = error => {
const parseErrorCode = (error) => {
if (error.response) {
if (error.response.status === 401) {
store.dispatch(onLogOut());
Expand All @@ -28,9 +29,9 @@ const API = axios.create();
API.defaults.baseURL = BASE_URL;
// Request parsing interceptor
API.interceptors.request.use(
async config => {
async (config) => {
const headers = await getHeaders();

config.baseURL = await getBaseUrl();
if (headers) {
config.headers = headers;
const { accountId } = headers;
Expand All @@ -41,13 +42,13 @@ API.interceptors.request.use(

return config;
},
error => Promise.reject(error),
(error) => Promise.reject(error),
);

// Response parsing interceptor
API.interceptors.response.use(
response => response,
error => parseErrorCode(error),
(response) => response,
(error) => parseErrorCode(error),
);

export default API;
7 changes: 7 additions & 0 deletions src/helpers/NavigationHelper.js
@@ -0,0 +1,7 @@
import * as React from 'react';

export const navigationRef = React.createRef();

export function navigate(name, params) {
navigationRef.current?.navigate(name, params);
}
9 changes: 9 additions & 0 deletions src/helpers/UrlHelper.js
@@ -0,0 +1,9 @@
import { store } from '../store';

export const getBaseUrl = async () => {
try {
const state = await store.getState();
const { installationUrl } = state.settings;
return installationUrl;
} catch (error) {}
};
23 changes: 15 additions & 8 deletions src/helpers/formHelper.js
Expand Up @@ -4,33 +4,40 @@ import t from 'tcomb-form-native';
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const nameRegex = /^.{2}/;
const passwordRegex = /^.{6}/;
const mobileNumberValidator = mobileNumber => {
const urlRegex = /^(?:(http|https):\/\/)?(?:[\w-]+\.)+[a-z]{2,6}$/;
const mobileNumberValidator = (mobileNumber) => {
const regex = /^(?:(?:\+|0{0,2})91(\s*[-]\s*)?|[0]?)?[789]\d{9}$/g;
return regex.test(mobileNumber);
};
export const IndianMobileRegex = t.refinement(t.Number, mobileNumberValidator);

export const isStringEmail = email => {
export const isStringEmail = (email) => {
const re = emailRegex;
return re.test(email);
};
export const Email = t.refinement(t.String, email => isStringEmail(email));
export const Email = t.refinement(t.String, (email) => isStringEmail(email));

export const isStringName = name => {
export const isStringName = (name) => {
const re = nameRegex;
return re.test(name);
};
export const Name = t.refinement(t.String, name => isStringName(name));
export const Name = t.refinement(t.String, (name) => isStringName(name));

export const isStringPassword = password => {
export const isStringUrl = (url) => {
const re = urlRegex;
return re.test(url);
};
export const URL = t.refinement(t.String, (url) => isStringUrl(url));

export const isStringPassword = (password) => {
const re = passwordRegex;
return re.test(password);
};
export const Password = t.refinement(t.String, password =>
export const Password = t.refinement(t.String, (password) =>
isStringPassword(password),
);

export const isNumberValid = number => {
export const isNumberValid = (number) => {
const regex = /^\+?\d{1,8}(?:\.\d{1,2})?$/;
return regex.test(number);
};
17 changes: 14 additions & 3 deletions src/helpers/index.js
Expand Up @@ -42,15 +42,26 @@ export const getRandomColor = function ({ userName }) {
// eslint-disable-next-line no-bitwise
hash = userName.charCodeAt(i) + ((hash << 5) - hash);
}
let colour = '#';
let color = '#';

for (let i = 0; i < 3; i++) {
// eslint-disable-next-line no-bitwise
let value = (hash >> (i * 8)) & 0xff;

colour += ('00' + value.toString(16)).substr(-2);
color += ('00' + value.toString(16)).substr(-2);
}
return colour;

return (
'#' +
color
.replace(/^#/, '')
.replace(/../g, (value) =>
(
'0' +
Math.min(255, Math.max(0, parseInt(value, 16) + -20)).toString(16)
).substr(-2),
)
);
};

export const checkImageExist = ({ thumbnail }) => {
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/en.json
Expand Up @@ -4,6 +4,12 @@
"HOME": "Home",
"SETTINGS": "Settings"
},
"CONFIGURE_URL": {
"ENTER_URL": "Chatwoot installation URL",
"URL_ERROR": "Enter a valid URL",
"NEXT": "Next",
"CHANGE_LANGUAGE": "Change language"
},
"LOGIN": {
"EMAIL": "Email",
"PASSWORD": "Password",
Expand Down
2 changes: 2 additions & 0 deletions src/reducer/index.js
Expand Up @@ -3,12 +3,14 @@ import locale from './locale';
import auth from './auth';
import inbox from './inbox';
import conversation from './conversation';
import settings from './settings';

const rootReducer = combineReducers({
locale,
auth,
inbox,
conversation,
settings,
});

export default (state, action) =>
Expand Down
41 changes: 41 additions & 0 deletions src/reducer/settings.js
@@ -0,0 +1,41 @@
import {
SET_URL,
SET_URL_SUCCESS,
SET_URL_ERROR,
RESET_SETTINGS,
} from '../constants/actions';
const initialState = {
installationUrl: null,
isUrlSet: false,
isSettingUrl: false,
error: {},
};
export default (state = initialState, action) => {
switch (action.type) {
case SET_URL:
return { ...state, isSettingUrl: true };

case SET_URL_SUCCESS:
return {
...state,
isSettingUrl: false,
isUrlSet: true,
installationUrl: action.payload,
error: {},
};
case SET_URL_ERROR:
return {
...state,
isSettingUrl: true,
isUrlSet: false,
error: action.payload,
installationUrl: null,
};

case RESET_SETTINGS:
return initialState;

default:
return state;
}
};

0 comments on commit 5d24737

Please sign in to comment.