Skip to content

Commit a8968a6

Browse files
committed
solved issue related to confirm dialog and better docker action file
1 parent 1e020ca commit a8968a6

10 files changed

Lines changed: 144 additions & 91 deletions

File tree

.github/workflows/docker-build-push.yml

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,65 +8,109 @@ on:
88

99
jobs:
1010
changes:
11-
runs-on: ubuntu-latest
11+
runs-on: ubuntu-24.04-arm
1212
outputs:
13-
backend: ${{ steps.filter.outputs.backend }}
14-
jobrunner: ${{ steps.filter.outputs.jobrunner }}
13+
backend: ${{ steps.set-filters.outputs.backend }}
14+
jobrunner: ${{ steps.set-filters.outputs.jobrunner }}
1515
steps:
16-
- uses: actions/checkout@v2
17-
- uses: dorny/paths-filter@v3
18-
id: filter
16+
- uses: actions/checkout@v4
17+
18+
- name: Run paths-filter only on push
19+
if: github.event_name == 'push'
20+
id: path-filter
21+
uses: dorny/paths-filter@v3
1922
with:
2023
filters: |
2124
backend:
2225
- 'Backend/**'
2326
jobrunner:
2427
- 'job-runner/**'
2528
29+
- name: Force all outputs on manual run
30+
if: github.event_name == 'workflow_dispatch'
31+
id: force-filter
32+
run: |
33+
echo "backend=true" >> $GITHUB_OUTPUT
34+
echo "jobrunner=true" >> $GITHUB_OUTPUT
35+
36+
- name: Set outputs depending on trigger
37+
id: set-filters
38+
run: |
39+
echo "backend=${{ steps.path-filter.outputs.backend || 'true' }}" >> $GITHUB_OUTPUT
40+
echo "jobrunner=${{ steps.path-filter.outputs.jobrunner || 'true' }}" >> $GITHUB_OUTPUT
41+
2642
backend:
2743
needs: changes
2844
if: needs.changes.outputs.backend == 'true'
29-
runs-on: ubuntu-latest
45+
runs-on: ubuntu-24.04-arm
3046
steps:
31-
- uses: actions/checkout@v2
47+
- uses: actions/checkout@v4
48+
49+
- name: Set up Docker Buildx
50+
uses: docker/setup-buildx-action@v3
51+
52+
- name: Prepare metadata
53+
id: meta
54+
run: |
55+
msg=$(echo "${{ github.event.head_commit.message }}" | head -n1)
56+
msg=${msg// /_}
57+
echo "short_msg=$msg" >> $GITHUB_OUTPUT
58+
echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
3259
3360
- name: Docker Hub Login
34-
uses: docker/login-action@v1
61+
uses: docker/login-action@v3
3562
with:
3663
username: ${{ secrets.DOCKER_USERNAME }}
3764
password: ${{ secrets.DOCKER_PASSWORD }}
3865

39-
- name: Set up Docker Buildx
40-
uses: docker/setup-buildx-action@v2
41-
4266
- name: Build & Push Backend
43-
run: |
44-
cd Backend
45-
docker buildx build \
46-
--platform linux/arm64 \
47-
-t sunjay195/cron-job-backend:latest \
48-
--push .
67+
uses: docker/build-push-action@v5
68+
with:
69+
context: ./Backend
70+
platforms: linux/arm64
71+
push: true
72+
tags: sunjay195/cron-job-backend:latest
73+
cache-from: type=gha
74+
cache-to: type=gha,mode=max
75+
labels: |
76+
git_commit_sha=${{ github.sha }}
77+
git_commit_short=${{ steps.meta.outputs.short_sha }}
78+
git_commit_msg=${{ steps.meta.outputs.short_msg }}
4979
5080
job-runner:
5181
needs: changes
5282
if: needs.changes.outputs.jobrunner == 'true'
53-
runs-on: ubuntu-latest
83+
runs-on: ubuntu-24.04-arm
5484
steps:
55-
- uses: actions/checkout@v2
85+
- uses: actions/checkout@v4
86+
87+
- name: Set up Docker Buildx
88+
uses: docker/setup-buildx-action@v3
89+
90+
- name: Prepare metadata
91+
id: meta
92+
run: |
93+
msg=$(echo "${{ github.event.head_commit.message }}" | head -n1)
94+
msg=${msg// /_}
95+
echo "short_msg=$msg" >> $GITHUB_OUTPUT
96+
echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
5697
5798
- name: Docker Hub Login
58-
uses: docker/login-action@v1
99+
uses: docker/login-action@v3
59100
with:
60101
username: ${{ secrets.DOCKER_USERNAME }}
61102
password: ${{ secrets.DOCKER_PASSWORD }}
62103

63-
- name: Set up Docker Buildx
64-
uses: docker/setup-buildx-action@v2
65-
66104
- name: Build & Push Job Runner
67-
run: |
68-
cd job-runner
69-
docker buildx build \
70-
--platform linux/arm64 \
71-
-t sunjay195/cron-job-runner:latest \
72-
--push .
105+
uses: docker/build-push-action@v5
106+
with:
107+
context: ./job-runner
108+
platforms: linux/arm64
109+
push: true
110+
tags: sunjay195/cron-job-runner:latest
111+
cache-from: type=gha
112+
cache-to: type=gha,mode=max
113+
labels: |
114+
git_commit_sha=${{ github.sha }}
115+
git_commit_short=${{ steps.meta.outputs.short_sha }}
116+
git_commit_msg=${{ steps.meta.outputs.short_msg }}

Backend/.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@ EMAIL_SERVICE_URL=http://localhost:3001
3737
EMAIL_SERVICE_SECRET=
3838

3939
# Qstash token (only if working with email queueing)
40-
QSTASH_TOKEN=
40+
QSTASH_TOKEN=
41+
42+
# REDIS CONFIGURATION
43+
REDIS_URL=

Backend/src/controllers/user.controllers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ export const handleForgotPassword = async (req: Request, res: Response, next: Ne
280280

281281
export const handleResetPassword = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
282282
const { token } = req.body;
283-
const { password } = req.body.trim();
283+
const password = req.body.password.trim();
284284

285285
try {
286286
const email = await redis.get(`otptoken:${token}`);

Frontend/src/components/settings/Preference.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { User } from '../../types'
1+
import type { UserWithoutEmail } from '../../types'
22

33
interface Props {
4-
details:User;
5-
setDetails: React.Dispatch<React.SetStateAction<User>>
4+
details: UserWithoutEmail;
5+
setDetails: React.Dispatch<React.SetStateAction<UserWithoutEmail>>
66
}
77
export default function Preference({ details, setDetails }: Props) {
88

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import { useEffect } from "react";
22
import { useBlocker } from "react-router-dom";
33

4-
export function useConfirmExit(isFilled: boolean) {
4+
export function useConfirmExit(isFilled: boolean, shouldBlock: boolean = true) {
55

66
useEffect(() => {
77
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
8-
if (!isFilled) return;
8+
if (!isFilled || !shouldBlock) return;
99
e.preventDefault();
1010
};
1111
window.addEventListener("beforeunload", handleBeforeUnload);
1212
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
13-
}, [isFilled]);
13+
}, [isFilled, shouldBlock]);
1414

1515
const blocker = useBlocker(
1616
({ currentLocation, nextLocation }) =>
17-
isFilled && currentLocation.pathname !== nextLocation.pathname,
17+
isFilled && shouldBlock && currentLocation.pathname !== nextLocation.pathname,
1818
);
1919

2020
useEffect(() => {
21-
if (blocker.state === "blocked") {
21+
if (shouldBlock && blocker.state === "blocked") {
2222
const ok = window.confirm("You have unsaved changes. Are you sure you want to leave this page?");
2323
if (ok) {
2424
blocker.proceed();
2525
} else {
2626
blocker.reset();
2727
}
2828
}
29-
}, [blocker]);
29+
}, [blocker, shouldBlock]);
3030
}

Frontend/src/pages/CreateJob.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ export default function CreateJob() {
3232
const [jobDetails, setJobDetails] = useState<JobDetails>(initialJobDetails);
3333

3434
const isFilled = JSON.stringify(jobDetails) !== JSON.stringify(initialJobDetails);
35-
useConfirmExit(isFilled);
36-
37-
35+
useConfirmExit(isFilled, !isLoading);
3836

3937
const navigate = useNavigate();
4038

Frontend/src/pages/EditJob.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export default function EditJob() {
3232
});
3333

3434
const isFilled = JSON.stringify(jobDetails) !== JSON.stringify(initialJobDetails);
35-
useConfirmExit(isFilled);
35+
useConfirmExit(isFilled, !isLoading);
3636

3737
useEffect(() => {
3838
setIsLoading(true)

Frontend/src/pages/Settings.tsx

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,20 @@ import { useEffect, useState } from 'react';
22
import { Pencil, Save } from "lucide-react";
33
import { useAppDispatch, useAppSelector } from '../hooks';
44
import { setAuth } from '../slices/authSlice';
5-
import type { User } from '../types';
6-
import { ConfirmMenu, Preference } from '../components';
5+
import type { User, UserWithoutEmail } from '../types';
6+
import { ConfirmMenu, Loader, Preference } from '../components';
77
import { useConfirmExit } from '../hooks/useConfirmExit';
88

99
export default function SettingsPage() {
1010
const user = useAppSelector(state => state.auth.user);
1111
const [isEditingName, setIsEditingName] = useState(false);
1212
const dispatch = useAppDispatch();
1313
const [confirmUpdate, setConfirmUpdate] = useState(false);
14-
const [initialDetails, setInitialDetails] = useState<User | null>(null);
15-
const [details, setDetails] = useState<User>({
14+
const [initialDetails, setInitialDetails] = useState<UserWithoutEmail | null>(null);
15+
const [isLoading, setIsLoading] = useState(false);
16+
17+
const [details, setDetails] = useState<UserWithoutEmail>({
1618
name: '',
17-
email: '',
1819
timezone: 'UTC',
1920
emailNotifications: true,
2021
pushAlerts: true,
@@ -23,25 +24,25 @@ export default function SettingsPage() {
2324
});
2425

2526
useEffect(() => {
26-
if (!user)
27-
return
27+
if (!user) return;
2828
const value = {
2929
name: user.name,
30-
email: user.email,
3130
timezone: user.timezone,
3231
emailNotifications: user.emailNotifications,
3332
pushAlerts: user.pushAlerts,
3433
mode: user.mode,
3534
timeFormat24: user.timeFormat24
36-
}
35+
};
3736
setDetails(value);
3837
setInitialDetails(value);
3938
}, [user]);
4039

4140
const isDirty = JSON.stringify(details) !== JSON.stringify(initialDetails);
42-
useConfirmExit(isDirty);
41+
useConfirmExit(isDirty, !isLoading);
4342

4443
const handleSaveChanges = () => {
44+
setIsLoading(true);
45+
if (!user) return;
4546

4647
fetch(`${import.meta.env.VITE_BACKEND_URL}/`, {
4748
method: "PUT",
@@ -61,6 +62,8 @@ export default function SettingsPage() {
6162
.then(data => {
6263
const userDetails: User = data.user;
6364
setDetails(userDetails);
65+
setInitialDetails(userDetails);
66+
setIsEditingName(false);
6467
dispatch(setAuth({
6568
user: {
6669
name: userDetails.name,
@@ -71,16 +74,18 @@ export default function SettingsPage() {
7174
emailNotifications: userDetails.emailNotifications,
7275
pushAlerts: userDetails.pushAlerts
7376
}
74-
}))
77+
}));
7578
})
7679
.catch(err => console.error(err))
80+
.finally(() => setIsLoading(false));
7781
}
7882

7983
if (!user) return <div>Loading user info...</div>;
8084

8185

8286
return (
8387
<>
88+
{isLoading && <Loader />}
8489
<h1 className="text-3xl text-purple-600 mb-6">Settings</h1>
8590

8691
<form onSubmit={e => {
@@ -89,43 +94,43 @@ export default function SettingsPage() {
8994
}} className="space-y-10 bg-white p-6 rounded-xl shadow">
9095

9196
<div className='border border-gray-200 rounded-lg px-4 py-6 space-y-6'>
92-
<h2 className="text-lg font-semibold text-gray-800 mb-4">Profile</h2>
93-
94-
<div className="space-y-4">
95-
<div>
96-
<label className="block mb-1 font-medium text-gray-700">Full Name</label>
97-
<div className="flex items-center gap-2">
98-
<input
99-
type="text"
100-
value={details.name}
101-
onChange={e => setDetails({ ...details, name: e.target.value })}
102-
readOnly={!isEditingName}
103-
className={`flex-1 rounded-md px-3 py-2 border transition
104-
${isEditingName
105-
? 'bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500'
106-
: 'bg-gray-100 text-gray-500 cursor-not-allowed border-gray-200'
107-
}`}
108-
/>
109-
<button
110-
type="button"
111-
onClick={() => setIsEditingName(!isEditingName)}
112-
className="px-3 py-2.5 bg-gray-100 border border-gray-300 rounded hover:text-purple-600 transition"
113-
title={isEditingName ? "Lock" : "Edit Name"}
114-
>
115-
{isEditingName ? <Save className="w-4 h-4" /> : <Pencil className="w-4 h-4" />}
116-
</button>
117-
</div>
118-
</div>
119-
<div>
120-
<label className="block mb-1 font-medium text-gray-700">Email</label>
97+
<h2 className="text-lg font-semibold text-gray-800 mb-4">Profile</h2>
98+
99+
<div className="space-y-4">
100+
<div>
101+
<label className="block mb-1 font-medium text-gray-700">Full Name</label>
102+
<div className="flex items-center gap-2">
121103
<input
122-
type="email"
123-
value={details.email}
124-
readOnly
125-
className="w-full mt-1 bg-gray-100 border border-gray-300 rounded px-3 py-2 text-gray-500 cursor-not-allowed"
104+
type="text"
105+
value={details.name}
106+
onChange={e => setDetails({ ...details, name: e.target.value })}
107+
readOnly={!isEditingName}
108+
className={`flex-1 rounded-md px-3 py-2 border transition
109+
${isEditingName
110+
? 'bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500'
111+
: 'bg-gray-100 text-gray-500 cursor-not-allowed border-gray-200'
112+
}`}
126113
/>
127-
</div>
114+
<button
115+
type="button"
116+
onClick={() => setIsEditingName(!isEditingName)}
117+
className="px-3 py-2.5 bg-gray-100 border border-gray-300 rounded hover:text-purple-600 transition"
118+
title={isEditingName ? "Lock" : "Edit Name"}
119+
>
120+
{isEditingName ? <Save className="w-4 h-4" /> : <Pencil className="w-4 h-4" />}
121+
</button>
122+
</div>
128123
</div>
124+
<div>
125+
<label className="block mb-1 font-medium text-gray-700">Email</label>
126+
<input
127+
type="email"
128+
value={user.email}
129+
readOnly
130+
className="w-full mt-1 bg-gray-100 border border-gray-300 rounded px-3 py-2 text-gray-500 cursor-not-allowed"
131+
/>
132+
</div>
133+
</div>
129134

130135
</div>
131136

0 commit comments

Comments
 (0)