update to latest react version and update client dependencies (#23)

This commit is contained in:
François Darveau 2023-08-03 23:24:56 -04:00 committed by GitHub
parent edc3e6c330
commit 9e19af7d4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 650 additions and 459 deletions

724
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,34 +3,34 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@mdi/js": "^6.4.95",
"@mdi/react": "^1.5.0",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.0.2",
"@types/node": "^16.11.6",
"@types/react": "^17.0.34",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-dom": "^17.0.11",
"@types/react-redux": "^7.1.20",
"@types/react-router-dom": "^5.1.7",
"axios": "^0.24.0",
"external-svg-loader": "^1.3.4",
"http-proxy-middleware": "^2.0.1",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^29.5.3",
"@types/node": "^20.4.6",
"@types/react": "^18.2.18",
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-dom": "^18.2.7",
"@types/react-redux": "^7.1.25",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.4.0",
"external-svg-loader": "^1.6.8",
"http-proxy-middleware": "^2.0.6",
"jwt-decode": "^3.1.2",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-router-dom": "^5.2.0",
"react-scripts": "5.0.1",
"redux": "^4.1.2",
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "^18.2.0",
"react-redux": "^8.1.2",
"react-router-dom": "^6.14.2",
"react-scripts": "^5.0.1",
"redux": "^4.2.1",
"redux-devtools-extension": "^2.13.9",
"redux-thunk": "^2.4.0",
"skycons-ts": "^0.2.0",
"typescript": "^4.4.4",
"web-vitals": "^2.1.2"
"redux-thunk": "^2.4.2",
"skycons-ts": "^1.0.0",
"typescript": "^4.9.5",
"web-vitals": "^3.4.0"
},
"scripts": {
"start": "react-scripts start",
@ -57,6 +57,6 @@
]
},
"devDependencies": {
"prettier": "^2.4.1"
"prettier": "^3.0.1"
}
}

View file

@ -2,7 +2,7 @@ import 'external-svg-loader';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { Apps } from './components/Apps/Apps';
@ -73,18 +73,18 @@ export const App = (): JSX.Element => {
if (!loading && !localStorage.theme) {
setTheme(parsePABToTheme(config.defaultTheme), false);
}
}, [loading]);
}, [config.defaultTheme, loading, setTheme]);
return (
<>
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/settings" component={Settings} />
<Route path="/applications" component={Apps} />
<Route path="/bookmarks" component={Bookmarks} />
<Route component={NotFound} />
</Switch>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/settings/*" element={<Settings/>} />
<Route path="/applications" element={<Apps searching={false}/>} />
<Route path="/bookmarks" element={<Bookmarks searching={false}/>} />
<Route element={<NotFound/>} />
</Routes>
</BrowserRouter>
<NotificationCenter />
</>

View file

@ -17,10 +17,8 @@ interface Props {
export const AppCard = (props: Props): JSX.Element => {
const { category, fromHomepage = false } = props;
const {
config: { config },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
const config = useSelector((state: State) => state.config.config);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const { setEditCategory } = bindActionCreators(actionCreators, dispatch);

View file

@ -22,9 +22,7 @@ export const AppGrid = (props: Props): JSX.Element => {
fromHomepage = false,
} = props;
const {
config: { config }
} = useSelector((state: State) => state);
const config = useSelector((state: State) => state.config.config);
const shouldBeShown = (category: Category) => {
return !config.hideEmptyCategories || category.apps.length > 0 || !fromHomepage

View file

@ -18,10 +18,8 @@ interface Props {
}
export const AppsTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
apps: { categoryInEdit },
config: { config },
} = useSelector((state: State) => state);
const categoryInEdit = useSelector((state: State) => state.apps.categoryInEdit);
const config = useSelector((state: State) => state.config.config);
const dispatch = useDispatch();
const {

View file

@ -23,10 +23,10 @@ export enum ContentType {
export const Apps = (props: Props): JSX.Element => {
// Get Redux state
const {
apps: { loading, categories, categoryInEdit },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
const categories = useSelector((state: State) => state.apps.categories);
const loading = useSelector((state: State) => state.apps.loading);
const categoryInEdit = useSelector((state: State) => state.apps.categoryInEdit);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
// Get Redux action creators
const dispatch = useDispatch();

View file

@ -26,7 +26,7 @@ export const AppsForm = ({
const { categories } = useSelector((state: State) => state.apps);
const dispatch = useDispatch();
const { addApp, updateApp, setEditApp, createNotification } =
const { addApp, updateApp, createNotification } =
bindActionCreators(actionCreators, dispatch);
const [useCustomIcon, toggleUseCustomIcon] = useState<boolean>(false);

View file

@ -18,10 +18,8 @@ interface Props {
}
export const AppsTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
apps: { categoryInEdit },
config: { config },
} = useSelector((state: State) => state);
const categoryInEdit = useSelector((state: State) => state.apps.categoryInEdit);
const config = useSelector((state: State) => state.config.config);
const dispatch = useDispatch();
const {

View file

@ -18,10 +18,8 @@ interface Props {
}
export const CategoryTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
config: { config },
apps: { categories },
} = useSelector((state: State) => state);
const categories = useSelector((state: State) => state.apps.categories);
const config = useSelector((state: State) => state.config.config);
const dispatch = useDispatch();
const {

View file

@ -16,11 +16,8 @@ interface Props {
export const BookmarkCard = (props: Props): JSX.Element => {
const { category, fromHomepage = false } = props;
const {
config: { config },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
const config = useSelector((state: State) => state.config.config);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const { setEditCategory } = bindActionCreators(actionCreators, dispatch);

View file

@ -22,9 +22,7 @@ export const BookmarkGrid = (props: Props): JSX.Element => {
fromHomepage = false,
} = props;
const {
config: { config }
} = useSelector((state: State) => state);
const config = useSelector((state: State) => state.config.config);
const shouldBeShown = (category: Category) => {
return !config.hideEmptyCategories || category.bookmarks.length > 0 || !fromHomepage

View file

@ -23,10 +23,10 @@ export enum ContentType {
export const Bookmarks = (props: Props): JSX.Element => {
// Get Redux state
const {
bookmarks: { loading, categories, categoryInEdit },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
const categories = useSelector((state: State) => state.bookmarks.categories);
const loading = useSelector((state: State) => state.bookmarks.loading);
const categoryInEdit = useSelector((state: State) => state.bookmarks.categoryInEdit);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
// Get Redux action creators
const dispatch = useDispatch();

View file

@ -18,10 +18,8 @@ interface Props {
}
export const BookmarksTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
bookmarks: { categoryInEdit },
config: { config },
} = useSelector((state: State) => state);
const categoryInEdit = useSelector((state: State) => state.bookmarks.categoryInEdit);
const config = useSelector((state: State) => state.config.config);
const dispatch = useDispatch();
const {

View file

@ -18,10 +18,8 @@ interface Props {
}
export const CategoryTable = ({ openFormForUpdating }: Props): JSX.Element => {
const {
config: { config },
bookmarks: { categories },
} = useSelector((state: State) => state);
const categories = useSelector((state: State) => state.bookmarks.categories);
const config = useSelector((state: State) => state.config.config);
const dispatch = useDispatch();
const {

View file

@ -16,12 +16,12 @@ import { Header } from './Header/Header';
import classes from './Home.module.css';
export const Home = (): JSX.Element => {
const {
apps: { categories: appCategories, loading: appsLoading },
bookmarks: { categories: bookmarkCategories, loading: bookmarksLoading },
config: { config },
auth: { isAuthenticated },
} = useSelector((state: State) => state);
const appCategories = useSelector((state: State) => state.apps.categories);
const appsLoading = useSelector((state: State) => state.apps.loading);
const bookmarkCategories = useSelector((state: State) => state.bookmarks.categories);
const bookmarksLoading = useSelector((state: State) => state.bookmarks.loading);
const config = useSelector((state: State) => state.config.config);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const { getCategories } = bindActionCreators(
@ -41,7 +41,7 @@ export const Home = (): JSX.Element => {
if (!appCategories.length && !bookmarkCategories.length) {
getCategories();
}
}, []);
}, [appCategories.length, bookmarkCategories.length]);
useEffect(() => {
if (localSearch) {
@ -81,7 +81,7 @@ export const Home = (): JSX.Element => {
setAppSearchResult(null);
setBookmarkSearchResult(null);
}
}, [localSearch]);
}, [appCategories, bookmarkCategories, localSearch]);
return (
<Container>

View file

@ -1,13 +1,15 @@
import { useSelector } from 'react-redux';
import { Redirect, Route, RouteProps } from 'react-router';
import { Navigate } from 'react-router';
import { Outlet } from 'react-router-dom';
import { State } from '../../store/reducers';
export const ProtectedRoute = ({ ...rest }: RouteProps) => {
export const ProtectedRoute = () => {
const { isAuthenticated } = useSelector((state: State) => state.auth);
if (isAuthenticated) {
return <Route {...rest} />;
return <Outlet />;
} else {
return <Redirect to="/settings/app" />;
return <Navigate to="/settings/app" />;
}
};

View file

@ -11,11 +11,9 @@ import { Button, InputGroup, SettingsHeadline } from '../../UI';
import { CustomQueries } from './CustomQueries/CustomQueries';
export const GeneralSettings = (): JSX.Element => {
const {
config: { loading, customQueries, config },
apps: { categories: appCategories },
bookmarks: { categories: bookmarkCategories },
} = useSelector((state: State) => state);
const config = useSelector((state: State) => state.config);
const appCategories = useSelector((state: State) => state.apps.categories);
const bookmarkCategories = useSelector((state: State) => state.bookmarks.categories);
const dispatch = useDispatch();
const { updateConfig, sortApps, sortCategories, sortBookmarks } =
@ -29,9 +27,9 @@ export const GeneralSettings = (): JSX.Element => {
// Get config
useEffect(() => {
setFormData({
...config,
...config.config,
});
}, [loading]);
}, [config]);
// Form handler
const formSubmitHandler = async (e: FormEvent) => {
@ -41,7 +39,7 @@ export const GeneralSettings = (): JSX.Element => {
await updateConfig(formData);
// Sort entities with new settings
if (formData.useOrdering !== config.useOrdering) {
if (formData.useOrdering !== config.config.useOrdering) {
sortCategories();
for (let { id } of appCategories) {
@ -166,7 +164,7 @@ export const GeneralSettings = (): JSX.Element => {
value={formData.defaultSearchProvider}
onChange={(e) => inputChangeHandler(e)}
>
{[...searchQueries.queries, ...customQueries].map((query: Query, idx) => {
{[...searchQueries.queries, ...config.customQueries].map((query: Query, idx) => {
const isCustom = idx >= searchQueries.queries.length;
return (
@ -189,7 +187,7 @@ export const GeneralSettings = (): JSX.Element => {
value={formData.secondarySearchProvider}
onChange={(e) => inputChangeHandler(e)}
>
{[...searchQueries.queries, ...customQueries].map((query: Query, idx) => {
{[...searchQueries.queries, ...config.customQueries].map((query: Query, idx) => {
const isCustom = idx >= searchQueries.queries.length;
return (

View file

@ -1,5 +1,5 @@
import { useSelector } from 'react-redux';
import { Link, NavLink, Route, Switch } from 'react-router-dom';
import { Link, NavLink, Route, Routes } from 'react-router-dom';
import { Route as SettingsRoute } from '../../interfaces';
import { State } from '../../store/reducers';
@ -22,6 +22,7 @@ import { WeatherSettings } from './WeatherSettings/WeatherSettings';
// UI
// Data
export const Settings = (): JSX.Element => {
const { isAuthenticated } = useSelector((state: State) => state.auth);
const tabs = isAuthenticated ? settings.routes : settings.routes.filter((r) => !r.authRequired);
@ -34,9 +35,13 @@ export const Settings = (): JSX.Element => {
<nav className={classes.SettingsNav}>
{tabs.map(({ name, dest }: SettingsRoute, idx) => (
<NavLink
className={classes.SettingsNavLink}
activeClassName={classes.SettingsNavLinkActive}
exact
className={({ isActive }) => {
const linkClasses = [classes.SettingsNavLink];
if (isActive) linkClasses.push(classes.SettingsNavLinkActive);
return linkClasses.join(" ");
}}
end
to={dest}
key={idx}
>
@ -47,24 +52,25 @@ export const Settings = (): JSX.Element => {
{/* ROUTES */}
<section className={classes.SettingsContent}>
<Switch>
<Route exact path="/settings" component={Themer} />
<ProtectedRoute
path="/settings/weather"
component={WeatherSettings}
/>
<ProtectedRoute
path="/settings/general"
component={GeneralSettings}
/>
<ProtectedRoute path="/settings/interface" component={UISettings} />
<ProtectedRoute
path="/settings/docker"
component={DockerSettings}
/>
<ProtectedRoute path="/settings/css" component={StyleSettings} />
<Route path="/settings/app" component={AppDetails} />
</Switch>
<Routes>
<Route path="" element={<Themer/>} />
<Route path="weather" element={<ProtectedRoute/>}>
<Route path="" element={<WeatherSettings/>}/>
</Route>
<Route path="general" element={<ProtectedRoute/>}>
<Route path="" element={<GeneralSettings/>}/>
</Route>
<Route path="interface" element={<ProtectedRoute/>}>
<Route path="" element={<UISettings/>}/>
</Route>
<Route path="docker" element={<ProtectedRoute/>}>
<Route path="" element={<DockerSettings/>}/>
</Route>
<Route path="css" element={<ProtectedRoute/>}>
<Route path="" element={<StyleSettings/>}/>
</Route>
<Route path="app" element={<AppDetails/>} />
</Routes>
</section>
</div>
</Container>

View file

@ -1,30 +1,27 @@
import { useState, useEffect } from 'react';
// Redux
import { useSelector, useDispatch } from 'react-redux';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Theme } from '../../../../interfaces';
import { actionCreators } from '../../../../store';
import { State } from '../../../../store/reducers';
// Other
import { Theme } from '../../../../interfaces';
// UI
import { Button, Modal } from '../../../UI';
import { ThemeGrid } from '../ThemeGrid/ThemeGrid';
import classes from './ThemeBuilder.module.css';
import { ThemeCreator } from './ThemeCreator';
import { ThemeEditor } from './ThemeEditor';
// Redux
// Other
// UI
interface Props {
themes: Theme[];
}
export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
const {
auth: { isAuthenticated },
theme: { themeInEdit, userThemes },
} = useSelector((state: State) => state);
const themeInEdit = useSelector((state: State) => state.theme.themeInEdit);
const userThemes = useSelector((state: State) => state.theme.userThemes);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
const { editTheme } = bindActionCreators(actionCreators, useDispatch());
@ -43,7 +40,7 @@ export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
toggleIsInEdit(false);
toggleShowModal(false);
}
}, [userThemes]);
}, [isInEdit, userThemes]);
return (
<div className={classes.ThemeBuilder}>

View file

@ -1,26 +1,24 @@
import { ChangeEvent, FormEvent, useState, useEffect } from 'react';
// Redux
import { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Theme } from '../../../../interfaces';
import { actionCreators } from '../../../../store';
import { State } from '../../../../store/reducers';
// UI
import { Button, InputGroup, ModalForm } from '../../../UI';
import classes from './ThemeCreator.module.css';
// Redux
// UI
// Other
import { Theme } from '../../../../interfaces';
interface Props {
modalHandler: () => void;
}
export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
const {
theme: { activeTheme, themeInEdit },
} = useSelector((state: State) => state);
activeTheme, themeInEdit
} = useSelector((state: State) => state.theme);
const { addTheme, updateTheme, editTheme } = bindActionCreators(
actionCreators,
@ -39,7 +37,7 @@ export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
useEffect(() => {
setFormData({ ...formData, colors: activeTheme.colors });
}, [activeTheme]);
}, [activeTheme.colors]);
useEffect(() => {
if (themeInEdit) {

View file

@ -1,23 +1,20 @@
import { Fragment } from 'react';
// Redux
import { useSelector, useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Theme } from '../../../../interfaces';
import { actionCreators } from '../../../../store';
import { State } from '../../../../store/reducers';
// Other
import { ActionIcons, CompactTable, Icon, ModalForm } from '../../../UI';
// Redux
// Other
interface Props {
modalHandler: () => void;
}
export const ThemeEditor = (props: Props): JSX.Element => {
const {
theme: { userThemes },
} = useSelector((state: State) => state);
const userThemes = useSelector((state: State) => state.theme.userThemes);
const { deleteTheme, editTheme } = bindActionCreators(
actionCreators,

View file

@ -1,32 +1,25 @@
import { ChangeEvent, FormEvent, Fragment, useEffect, useState } from 'react';
// Redux
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Theme, ThemeSettingsForm } from '../../../interfaces';
import { actionCreators } from '../../../store';
import { State } from '../../../store/reducers';
// Typescript
import { Theme, ThemeSettingsForm } from '../../../interfaces';
// Components
import { inputHandler, parseThemeToPAB, themeSettingsTemplate } from '../../../utility';
import { Button, InputGroup, SettingsHeadline, Spinner } from '../../UI';
import { ThemeBuilder } from './ThemeBuilder/ThemeBuilder';
import { ThemeGrid } from './ThemeGrid/ThemeGrid';
// Redux
// Typescript
// Components
// Other
import {
inputHandler,
parseThemeToPAB,
themeSettingsTemplate,
} from '../../../utility';
export const Themer = (): JSX.Element => {
const {
auth: { isAuthenticated },
config: { loading, config },
theme: { themes, userThemes },
} = useSelector((state: State) => state);
export const Themer = (): JSX.Element => {
const themes = useSelector((state: State) => state.theme.themes);
const userThemes = useSelector((state: State) => state.theme.userThemes);
const config = useSelector((state: State) => state.config.config);
const loading = useSelector((state: State) => state.config.loading);
const isAuthenticated = useSelector((state: State) => state.auth.isAuthenticated);
const dispatch = useDispatch();
const { updateConfig } = bindActionCreators(actionCreators, dispatch);

View file

@ -1,21 +1,18 @@
import { useState, ChangeEvent, useEffect, FormEvent } from 'react';
import axios from 'axios';
// Redux
import { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ApiResponse, Weather, WeatherForm } from '../../../interfaces';
import { actionCreators } from '../../../store';
import { State } from '../../../store/reducers';
// Typescript
import { ApiResponse, Weather, WeatherForm } from '../../../interfaces';
// UI
import { InputGroup, Button, SettingsHeadline } from '../../UI';
// Utils
import { inputHandler, weatherSettingsTemplate } from '../../../utility';
import { Button, InputGroup, SettingsHeadline } from '../../UI';
// Redux
// Typescript
// UI
// Utils
export const WeatherSettings = (): JSX.Element => {
const { loading, config } = useSelector((state: State) => state.config);

View file

@ -1,20 +1,17 @@
import { useState, useEffect, Fragment } from 'react';
import axios from 'axios';
// Redux
import { Fragment, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
// Typescript
import { Weather, ApiResponse } from '../../../interfaces';
// CSS
import classes from './WeatherWidget.module.css';
// UI
import { WeatherIcon } from '../../UI';
import { ApiResponse, Weather } from '../../../interfaces';
import { State } from '../../../store/reducers';
import { weatherTemplate } from '../../../utility/templateObjects/weatherTemplate';
import { WeatherIcon } from '../../UI';
import classes from './WeatherWidget.module.css';
// Redux
// Typescript
// CSS
// UI
export const WeatherWidget = (): JSX.Element => {
const { loading: configLoading, config } = useSelector(
(state: State) => state.config

View file

@ -1,17 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './store/store';
import { App } from './App';
import { store } from './store/store';
ReactDOM.render(
const container = document.getElementById('root');
const root = createRoot(container!); // createRoot(container!) if you use TypeScript
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
</React.StrictMode>);

View file

@ -61,7 +61,7 @@ export const autoLogin = () => async (dispatch: Dispatch<AutoLoginAction>) => {
export const authError =
(error: unknown, showNotification: boolean) =>
(dispatch: Dispatch<AuthErrorAction>) => {
const apiError = error as AxiosError;
const apiError = error as AxiosError<any, any>;
if (showNotification) {
dispatch<any>({

View file

@ -1,6 +1,7 @@
import axios from 'axios';
import { store } from '../store/store';
import { createNotification } from '../store/action-creators';
import { store } from '../store/store';
export const checkVersion = async (isForced: boolean = false) => {
try {

View file

@ -2,5 +2,5 @@
* https://stackoverflow.com/a/30851002/16957052
*/
export const escapeRegex = (exp: string) => {
return exp.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&');
return exp.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g, '\\$&');
};

View file

@ -1,6 +1,6 @@
export const isUrlOrIp = (data: string): boolean => {
const regex =
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^((http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/i;
/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?|^((http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/i;
return regex.test(data);
};