Skip to content

Commit 1e020ca

Browse files
committed
added confirm dialog for forms
1 parent 9c1848a commit 1e020ca

6 files changed

Lines changed: 100 additions & 65 deletions

File tree

Frontend/src/App.tsx

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,6 @@
1-
import { BrowserRouter, Route, Routes } from 'react-router-dom';
2-
import { Home, Login, Signup, Dashboard, CreateJob, Jobs, ResetPassword, EditJob, Logs, JobLogs, Settings, NotFoundPage, ForgotPassword, VerifyEmail, PrivacyPolicy, Terms } from './pages';
3-
import { Layout, ProtectedRoute, PublicRoute } from './components';
1+
import { RouterProvider } from "react-router-dom";
2+
import { router } from "./routes";
43

54
export default function App() {
6-
7-
return (
8-
<BrowserRouter>
9-
<Routes>
10-
<Route path='/' element={<Home />} />
11-
<Route path='/privacy-policy' element={<PrivacyPolicy />} />
12-
<Route path='/terms' element={<Terms />} />
13-
14-
<Route path="/login" element={
15-
<PublicRoute>
16-
<Login />
17-
</PublicRoute>
18-
} />
19-
20-
<Route path="/signup" element={
21-
<PublicRoute>
22-
<Signup />
23-
</PublicRoute>
24-
} />
25-
26-
<Route path="/forgot-password" element={
27-
<PublicRoute>
28-
<ForgotPassword />
29-
</PublicRoute>
30-
} />
31-
32-
<Route path="/verify-email/:userId" element={
33-
<VerifyEmail />
34-
} />
35-
36-
<Route path="/reset-password/:token" element={
37-
<ResetPassword />
38-
} />
39-
40-
<Route element={<ProtectedRoute />}>
41-
<Route element={<Layout />}>
42-
<Route path="/dashboard" element={<Dashboard />} />
43-
<Route path="/create" element={<CreateJob />} />
44-
<Route path="/jobs" element={<Jobs />} />
45-
<Route path="/job/:jobId/logs" element={<JobLogs />} />
46-
<Route path="/job/:jobId/edit" element={<EditJob />} />
47-
<Route path="/logs" element={<Logs />} />
48-
<Route path="/settings" element={<Settings />} />
49-
</Route>
50-
</Route>
51-
52-
<Route path='*' element={<NotFoundPage />} />
53-
</Routes>
54-
</BrowserRouter>
55-
)
56-
}
5+
return <RouterProvider router={router} />;
6+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useEffect } from "react";
2+
import { useBlocker } from "react-router-dom";
3+
4+
export function useConfirmExit(isFilled: boolean) {
5+
6+
useEffect(() => {
7+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
8+
if (!isFilled) return;
9+
e.preventDefault();
10+
};
11+
window.addEventListener("beforeunload", handleBeforeUnload);
12+
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
13+
}, [isFilled]);
14+
15+
const blocker = useBlocker(
16+
({ currentLocation, nextLocation }) =>
17+
isFilled && currentLocation.pathname !== nextLocation.pathname,
18+
);
19+
20+
useEffect(() => {
21+
if (blocker.state === "blocked") {
22+
const ok = window.confirm("You have unsaved changes. Are you sure you want to leave this page?");
23+
if (ok) {
24+
blocker.proceed();
25+
} else {
26+
blocker.reset();
27+
}
28+
}
29+
}, [blocker]);
30+
}

Frontend/src/pages/CreateJob.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useAppDispatch, useAppSelector } from '../hooks';
66
import { addJob } from '../slices/jobSlice';
77
import type { JobDetails } from '../types';
88
import { jobSchema } from '../schemas/jobSchemas';
9+
import { useConfirmExit } from '../hooks/useConfirmExit';
910

1011
export default function CreateJob() {
1112
const [tab, setTab] = useState<'common' | 'advanced'>('common');
@@ -14,7 +15,8 @@ export default function CreateJob() {
1415
const user = useAppSelector(state => state.auth.user);
1516
const dispatch = useAppDispatch();
1617
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
17-
const [jobDetails, setJobDetails] = useState<JobDetails>({
18+
19+
const initialJobDetails: JobDetails = {
1820
name: '',
1921
url: 'https://',
2022
method: 'GET',
@@ -25,7 +27,14 @@ export default function CreateJob() {
2527
timezone: user?.timezone || 'UTC',
2628
timeout: 30,
2729
email: true
28-
});
30+
};
31+
32+
const [jobDetails, setJobDetails] = useState<JobDetails>(initialJobDetails);
33+
34+
const isFilled = JSON.stringify(jobDetails) !== JSON.stringify(initialJobDetails);
35+
useConfirmExit(isFilled);
36+
37+
2938

3039
const navigate = useNavigate();
3140

Frontend/src/pages/EditJob.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ import type { JobDetails } from '../types'
66
import { useAppDispatch } from '../hooks';
77
import { updateJob } from '../slices/jobSlice';
88
import { jobSchema } from '../schemas/jobSchemas';
9+
import { useConfirmExit } from '../hooks/useConfirmExit';
910

1011
export default function EditJob() {
1112
const { jobId } = useParams();
13+
const dispatch = useAppDispatch();
1214
const navigate = useNavigate();
1315
const [tab, setTab] = useState<'common' | 'advanced'>('common');
1416
const [confirmEdit, setConfirmEdit] = useState(false);
1517
const [confirmAddJsonHeader, setConfirmAddJsonHeader] = useState(false);
1618
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null);
1719
const [isLoading, setIsLoading] = useState(false);
20+
const [initialJobDetails, setInitialJobDetails] = useState<JobDetails | null>(null);
1821
const [jobDetails, setJobDetails] = useState<JobDetails>({
1922
name: '',
2023
url: 'https://',
@@ -28,7 +31,8 @@ export default function EditJob() {
2831
email: true
2932
});
3033

31-
const dispatch = useAppDispatch();
34+
const isFilled = JSON.stringify(jobDetails) !== JSON.stringify(initialJobDetails);
35+
useConfirmExit(isFilled);
3236

3337
useEffect(() => {
3438
setIsLoading(true)
@@ -46,8 +50,7 @@ export default function EditJob() {
4650
.then(res => {
4751
if (!Array.isArray(res) || res.length === 0) return;
4852
const job = res[0];
49-
50-
setJobDetails({
53+
const details = {
5154
name: job.data.name,
5255
method: job.data.method,
5356
url: job.data.url,
@@ -60,8 +63,9 @@ export default function EditJob() {
6063
headers: job.data?.headers && typeof job.data.headers === 'object'
6164
? Object.entries(job.data.headers).map(([key, value]) => ({ key, value: String(value) }))
6265
: []
63-
})
64-
66+
};
67+
setJobDetails(details)
68+
setInitialJobDetails(details);
6569
}).catch(err => {
6670
console.error(err);
6771
navigate("/jobs")

Frontend/src/pages/Settings.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { useAppDispatch, useAppSelector } from '../hooks';
44
import { setAuth } from '../slices/authSlice';
55
import type { User } from '../types';
66
import { ConfirmMenu, Preference } from '../components';
7+
import { useConfirmExit } from '../hooks/useConfirmExit';
78

89
export default function SettingsPage() {
910
const user = useAppSelector(state => state.auth.user);
1011
const [isEditingName, setIsEditingName] = useState(false);
1112
const dispatch = useAppDispatch();
12-
const [confirmUpdate, setConfirmUpdate] = useState(false)
13+
const [confirmUpdate, setConfirmUpdate] = useState(false);
14+
const [initialDetails, setInitialDetails] = useState<User | null>(null);
1315
const [details, setDetails] = useState<User>({
1416
name: '',
1517
email: '',
@@ -23,18 +25,22 @@ export default function SettingsPage() {
2325
useEffect(() => {
2426
if (!user)
2527
return
26-
27-
setDetails({
28+
const value = {
2829
name: user.name,
2930
email: user.email,
3031
timezone: user.timezone,
3132
emailNotifications: user.emailNotifications,
3233
pushAlerts: user.pushAlerts,
3334
mode: user.mode,
3435
timeFormat24: user.timeFormat24
35-
})
36+
}
37+
setDetails(value);
38+
setInitialDetails(value);
3639
}, [user]);
3740

41+
const isDirty = JSON.stringify(details) !== JSON.stringify(initialDetails);
42+
useConfirmExit(isDirty);
43+
3844
const handleSaveChanges = () => {
3945

4046
fetch(`${import.meta.env.VITE_BACKEND_URL}/`, {

Frontend/src/routes.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { createBrowserRouter } from "react-router-dom";
2+
import { Home, Login, Signup, Dashboard, CreateJob, Jobs, ResetPassword, EditJob, Logs, JobLogs, Settings, NotFoundPage, ForgotPassword, VerifyEmail, PrivacyPolicy, Terms } from "./pages";
3+
import { Layout, ProtectedRoute, PublicRoute } from "./components";
4+
5+
export const router = createBrowserRouter([
6+
{ path: "/", element: <Home /> },
7+
{ path: "/privacy-policy", element: <PrivacyPolicy /> },
8+
{ path: "/terms", element: <Terms /> },
9+
10+
{ path: "/login", element: <PublicRoute><Login /></PublicRoute> },
11+
{ path: "/signup", element: <PublicRoute><Signup /></PublicRoute> },
12+
{ path: "/forgot-password", element: <PublicRoute><ForgotPassword /></PublicRoute> },
13+
14+
{ path: "/verify-email/:userId", element: <VerifyEmail /> },
15+
{ path: "/reset-password/:token", element: <ResetPassword /> },
16+
17+
{
18+
element: <ProtectedRoute />,
19+
children: [
20+
{
21+
element: <Layout />,
22+
children: [
23+
{ path: "/dashboard", element: <Dashboard /> },
24+
{ path: "/create", element: <CreateJob /> },
25+
{ path: "/jobs", element: <Jobs /> },
26+
{ path: "/job/:jobId/logs", element: <JobLogs /> },
27+
{ path: "/job/:jobId/edit", element: <EditJob /> },
28+
{ path: "/logs", element: <Logs /> },
29+
{ path: "/settings", element: <Settings /> },
30+
]
31+
}
32+
]
33+
},
34+
35+
{ path: "*", element: <NotFoundPage /> }
36+
]);

0 commit comments

Comments
 (0)