diff --git a/package-lock.json b/package-lock.json
index fee9f1e..aa56a82 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@types/lodash": "^4.17.5",
"formik": "^2.4.6",
"lodash": "^4.17.21",
+ "notistack": "^3.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
@@ -4393,6 +4394,14 @@
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
"dev": true
},
+ "node_modules/goober": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz",
+ "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==",
+ "peerDependencies": {
+ "csstype": "^3.0.10"
+ }
+ },
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -5417,6 +5426,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/notistack": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz",
+ "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==",
+ "dependencies": {
+ "clsx": "^1.1.0",
+ "goober": "^2.0.33"
+ },
+ "engines": {
+ "node": ">=12.0.0",
+ "npm": ">=6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/notistack"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/notistack/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
diff --git a/package.json b/package.json
index e6a8d16..062da09 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"@types/lodash": "^4.17.5",
"formik": "^2.4.6",
"lodash": "^4.17.21",
+ "notistack": "^3.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
diff --git a/src/components/App.tsx b/src/components/App.tsx
index 11f665a..ee7e794 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -1,26 +1,41 @@
-import { BrowserRouter, useRoutes } from 'react-router-dom'
-import routesConfig from '../config/routesConfig'
-import Navbar from './Navbar'
-import { Suspense } from 'react'
-import Loading from './Loading'
-import { ThemeProvider } from '@mui/material'
-import theme from '../config/muiConfig'
+import { BrowserRouter, useRoutes } from 'react-router-dom';
+import routesConfig from '../config/routesConfig';
+import Navbar from './Navbar';
+import { Suspense } from 'react';
+import Loading from './Loading';
+import { ThemeProvider } from '@mui/material';
+import theme from '../config/muiConfig';
+import { CustomSnackBarProvider } from '../contexts/CustomSnackBarContext';
+import { SnackbarProvider } from 'notistack';
const AppRoutes = () => {
- return useRoutes(routesConfig)
-}
+ return useRoutes(routesConfig);
+};
function App() {
return (
-
-
- }>
-
-
-
+
+
+
+
+ }>
+
+
+
+
+
- )
+ );
}
-export default App
+export default App;
diff --git a/src/components/result/ToolTextResult.tsx b/src/components/result/ToolTextResult.tsx
index 916b132..6b14acf 100644
--- a/src/components/result/ToolTextResult.tsx
+++ b/src/components/result/ToolTextResult.tsx
@@ -1,11 +1,17 @@
-import Typography from '@mui/material/Typography'
-import { Box, Stack, TextField } from '@mui/material'
-import Button from '@mui/material/Button'
-import DownloadIcon from '@mui/icons-material/Download'
-import ContentPasteIcon from '@mui/icons-material/ContentPaste'
-import React from 'react'
+import Typography from '@mui/material/Typography';
+import { Box, Stack, TextField } from '@mui/material';
+import Button from '@mui/material/Button';
+import DownloadIcon from '@mui/icons-material/Download';
+import ContentPasteIcon from '@mui/icons-material/ContentPaste';
+import React from 'react';
-export default function ToolTextResult({ title = 'Result', value }: { title?: string; value: string }) {
+export default function ToolTextResult({
+ title = 'Result',
+ value
+}: {
+ title?: string;
+ value: string;
+}) {
return (
@@ -17,5 +23,5 @@ export default function ToolTextResult({ title = 'Result', value }: { title?: st
}>Copy to clipboard
- )
+ );
}
diff --git a/src/config/muiConfig.ts b/src/config/muiConfig.ts
index 0aeb41b..118efef 100644
--- a/src/config/muiConfig.ts
+++ b/src/config/muiConfig.ts
@@ -1,4 +1,4 @@
-import { createTheme } from '@mui/material'
+import { createTheme } from '@mui/material';
const theme = createTheme({
typography: {
@@ -6,7 +6,8 @@ const theme = createTheme({
textTransform: 'none'
}
},
- palette: { background: { default: '#ebf5ff' } }
-})
+ palette: { background: { default: '#ebf5ff' } },
+ zIndex: { snackbar: 100000 }
+});
-export default theme
+export default theme;
diff --git a/src/contexts/CustomSnackBarContext.tsx b/src/contexts/CustomSnackBarContext.tsx
new file mode 100644
index 0000000..0fa50c6
--- /dev/null
+++ b/src/contexts/CustomSnackBarContext.tsx
@@ -0,0 +1,35 @@
+import { createContext, FC, ReactNode } from 'react';
+import { Zoom } from '@mui/material';
+import { useSnackbar } from 'notistack';
+
+type CustomSnackBarContext = {
+ showSnackBar: (message: string, type: 'error' | 'success') => void;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-redeclare
+export const CustomSnackBarContext = createContext(
+ {} as CustomSnackBarContext
+);
+
+interface Props {
+ children: ReactNode;
+}
+
+export const CustomSnackBarProvider: FC = ({ children }) => {
+ const { enqueueSnackbar } = useSnackbar();
+ const showSnackBar = (message: string, type: 'error' | 'success') => {
+ enqueueSnackbar(message, {
+ variant: type,
+ anchorOrigin: {
+ vertical: 'top',
+ horizontal: 'right'
+ },
+ TransitionComponent: Zoom
+ });
+ };
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/pages/string/split/index.tsx b/src/pages/string/split/index.tsx
index ff04303..9e11ded 100644
--- a/src/pages/string/split/index.tsx
+++ b/src/pages/string/split/index.tsx
@@ -3,13 +3,14 @@ import ToolLayout from '../../../components/ToolLayout';
import { Box, Stack, TextField } from '@mui/material';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
-import React, { useEffect, useRef, useState } from 'react';
+import React, { useContext, useEffect, useRef, useState } from 'react';
import ToolTextInput from '../../../components/input/ToolTextInput';
import ToolTextResult from '../../../components/result/ToolTextResult';
import { Field, Formik, FormikProps, useFormikContext } from 'formik';
import * as Yup from 'yup';
import ToolOptions from '../../../components/ToolOptions';
import { splitIntoChunks, splitTextByLength } from './service';
+import { CustomSnackBarContext } from '../../../contexts/CustomSnackBarContext';
type SplitOperatorType = 'symbol' | 'regex' | 'length' | 'chunks';
const initialValues = {
@@ -142,38 +143,45 @@ export default function SplitText() {
const [input, setInput] = useState('');
const [result, setResult] = useState('');
const formRef = useRef>(null);
+ const { showSnackBar } = useContext(CustomSnackBarContext);
+
const FormikListenerComponent = () => {
const { values } = useFormikContext();
useEffect(() => {
- const {
- splitSeparatorType,
- outputSeparator,
- charBeforeChunk,
- charAfterChunk,
- chunksValue,
- symbolValue,
- regexValue,
- lengthValue
- } = values;
- let splitText;
- switch (splitSeparatorType) {
- case 'symbol':
- splitText = input.split(symbolValue);
- break;
- case 'regex':
- splitText = input.split(new RegExp(regexValue));
- break;
- case 'length':
- splitText = splitTextByLength(input, Number(lengthValue));
- break;
- case 'chunks':
- splitText = splitIntoChunks(input, Number(chunksValue)).map(
- (chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}`
- );
+ try {
+ const {
+ splitSeparatorType,
+ outputSeparator,
+ charBeforeChunk,
+ charAfterChunk,
+ chunksValue,
+ symbolValue,
+ regexValue,
+ lengthValue
+ } = values;
+ let splitText;
+ switch (splitSeparatorType) {
+ case 'symbol':
+ splitText = input.split(symbolValue);
+ break;
+ case 'regex':
+ splitText = input.split(new RegExp(regexValue));
+ break;
+ case 'length':
+ splitText = splitTextByLength(input, Number(lengthValue));
+ break;
+ case 'chunks':
+ splitText = splitIntoChunks(input, Number(chunksValue)).map(
+ (chunk) => `${charBeforeChunk}${chunk}${charAfterChunk}`
+ );
+ }
+ const res = splitText.join(outputSeparator);
+ setResult(res);
+ } catch (exception: unknown) {
+ if (exception instanceof Error)
+ showSnackBar(exception.message, 'error');
}
- const res = splitText.join(outputSeparator);
- setResult(res);
}, [values, input]);
return null; // This component doesn't render anything