Skip to content

Commit f83add7

Browse files
committed
added appointment
1 parent 5790da7 commit f83add7

20 files changed

Lines changed: 1580 additions & 190 deletions

File tree

api-gateway/src/app.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ import healthRoutes from './routes/health';
44
import { patientAuthProxy } from './proxy/patient.auth.proxy';
55
import { patientDataProxy } from './proxy/patient.data.proxy';
66
import { staffAuthProxy } from './proxy/staff.auth.proxy';
7-
// import { staffDataProxy } from './proxy/staff.data.proxy';
87
import staffDataRouter from './routes/staff.data.router';
98
import { authenticate } from './middlewares/auth.middleware';
109
import { requirePatientSelf } from './middlewares/patient.guard';
10+
import { createProxyMiddleware } from 'http-proxy-middleware';
1111

1212
const app = express();
13+
1314
app.use(
1415
cors({
1516
origin: 'http://localhost:3000',
@@ -20,12 +21,34 @@ app.use(
2021
// health
2122
app.use('/health', healthRoutes);
2223

23-
// 🔓 PUBLIC auth (NO JWT)
24+
// PUBLIC auth
2425
app.use('/patients/public', patientAuthProxy);
25-
// 🔐 PROTECTED patient data
26+
27+
// PATIENT self routes
2628
app.use('/patients', authenticate, requirePatientSelf, patientDataProxy);
2729

30+
// STAFF auth
2831
app.use('/staff/public', staffAuthProxy);
2932
app.use('/staff', authenticate, staffDataRouter);
3033

34+
//Appointment routes (accessible by PATIENT + STAFF + ADMIN)
35+
36+
app.use(
37+
'/appointments',
38+
authenticate,
39+
(req: any, _res, next) => {
40+
if (req.user) {
41+
req.headers['x-user-id'] = req.user.sub;
42+
req.headers['x-user-role'] = req.user.role;
43+
req.headers['x-user-type'] = req.user.type;
44+
}
45+
next();
46+
},
47+
createProxyMiddleware({
48+
target: process.env.PATIENT_SERVICE_URL,
49+
changeOrigin: true,
50+
pathRewrite: (path) => `/appointments${path}`,
51+
})
52+
);
53+
3154
export default app;

api-gateway/src/middlewares/auth.middleware.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { Request, Response, NextFunction } from 'express';
22
import jwt from 'jsonwebtoken';
33
import { error } from 'node:console';
44

5+
console.log('JWT_SECRET in gateway:', process.env.JWT_SECRET);
6+
57
export interface AuthenticatedRequest extends Request {
68
user?: {
79
sub: string;

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"axios": "^1.13.5",
13+
"lucide-react": "^0.575.0",
1314
"next": "16.1.6",
1415
"react": "19.2.3",
1516
"react-dom": "19.2.3",

frontend/pnpm-lock.yaml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/public/hospital.jpg

2.47 MB
Loading

frontend/src/app/login/page.tsx

Lines changed: 187 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { useState } from 'react';
44
import { loginPatient } from '../../auth/auth.service';
55
import { useAuth } from '../../auth/auth.provider';
66
import { useRouter } from 'next/navigation';
7+
import { Eye,EyeOff } from 'lucide-react';
78
import Link from 'next/link';
9+
import Image from 'next/image';
810

911
export default function LoginPage() {
1012
const { loginSuccess } = useAuth();
@@ -14,6 +16,7 @@ export default function LoginPage() {
1416
const [password, setPassword] = useState('');
1517
const [error, setError] = useState('');
1618
const [loading, setLoading] = useState(false);
19+
const [showPassword, setShowPassword] = useState(false);
1720

1821
async function handleLogin(e: React.FormEvent) {
1922
e.preventDefault();
@@ -32,71 +35,196 @@ export default function LoginPage() {
3235
}
3336

3437
return (
35-
<div className="min-h-screen flex items-center justify-center bg-gray-100 px-4">
36-
<div className="w-full max-w-md bg-white rounded-lg shadow-md p-8">
37-
<h1 className="text-2xl font-semibold text-gray-800 text-center mb-6">
38-
Patient Login
39-
</h1>
40-
41-
<form onSubmit={handleLogin} className="space-y-4">
42-
{/* Email */}
43-
<div>
44-
<label className="block text-sm font-medium text-gray-700 mb-1">
45-
Email
46-
</label>
47-
<input
48-
type="email"
49-
value={email}
50-
onChange={(e) => setEmail(e.target.value)}
51-
required
52-
placeholder="you@example.com"
53-
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
54-
/>
38+
<div className="min-h-screen flex bg-white">
39+
40+
{/* LEFT SIDE (FORM) */}
41+
<div className="w-full lg:w-1/2 flex items-center justify-center px-8 lg:px-20 py-16">
42+
<div className="w-full max-w-md space-y-6">
43+
44+
{/* Logo */}
45+
<div className="flex items-center gap-3">
46+
<div className="w-9 h-9 bg-teal-600 rounded-lg flex items-center justify-center text-white font-semibold">
47+
+
48+
</div>
49+
<span className="text-lg font-semibold text-gray-800">
50+
MedFlow
51+
</span>
5552
</div>
5653

57-
{/* Password */}
54+
{/* Heading */}
5855
<div>
59-
<label className="block text-sm font-medium text-gray-700 mb-1">
60-
Password
61-
</label>
62-
<input
63-
type="password"
64-
value={password}
65-
onChange={(e) => setPassword(e.target.value)}
66-
required
67-
placeholder="••••••••"
68-
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
69-
/>
56+
<h1 className="text-3xl font-semibold text-gray-900">
57+
Welcome back
58+
</h1>
59+
<p className="text-gray-500 mt-2">
60+
Sign in to your MedFlow account
61+
</p>
62+
</div>
63+
64+
{/* Google Button */}
65+
<button
66+
type="button"
67+
className="w-full border border-gray-300 rounded-lg py-3 flex items-center justify-center gap-3 hover:bg-gray-50 transition"
68+
>
69+
<svg
70+
className="w-5 h-5"
71+
viewBox="0 0 48 48"
72+
>
73+
<path
74+
fill="#EA4335"
75+
d="M24 9.5c3.54 0 6.7 1.22 9.19 3.6l6.85-6.85C35.91 2.73 30.42 0 24 0 14.64 0 6.56 5.4 2.56 13.28l7.98 6.19C12.27 13.09 17.65 9.5 24 9.5z"
76+
/>
77+
<path
78+
fill="#4285F4"
79+
d="M46.5 24.5c0-1.63-.14-3.19-.4-4.68H24v9.06h12.68c-.55 2.96-2.24 5.47-4.77 7.16l7.35 5.72C43.73 37.32 46.5 31.47 46.5 24.5z"
80+
/>
81+
<path
82+
fill="#FBBC05"
83+
d="M10.54 28.47A14.45 14.45 0 019.5 24c0-1.55.27-3.04.75-4.47l-7.98-6.19A23.98 23.98 0 000 24c0 3.9.93 7.59 2.56 10.72l7.98-6.25z"
84+
/>
85+
<path
86+
fill="#34A853"
87+
d="M24 48c6.48 0 11.93-2.14 15.91-5.82l-7.35-5.72c-2.04 1.37-4.65 2.18-8.56 2.18-6.35 0-11.73-3.59-13.46-8.78l-7.98 6.25C6.56 42.6 14.64 48 24 48z"
88+
/>
89+
</svg>
90+
91+
<span className="text-sm font-medium text-gray-700">
92+
Continue with Google
93+
</span>
94+
</button>
95+
96+
{/* Divider */}
97+
<div className="flex items-center gap-4">
98+
<div className="flex-1 h-px bg-gray-200" />
99+
<span className="text-xs text-gray-400">OR</span>
100+
<div className="flex-1 h-px bg-gray-200" />
70101
</div>
71102

72-
{/* Error */}
73-
{error && (
74-
<div className="rounded-md bg-red-50 border border-red-200 p-2 text-sm text-red-600">
75-
{error}
103+
{/* FORM */}
104+
<form onSubmit={handleLogin} className="space-y-5">
105+
106+
{/* Email */}
107+
<div>
108+
<label className="text-sm font-medium text-gray-700">
109+
Email address
110+
</label>
111+
<input
112+
type="email"
113+
value={email}
114+
onChange={(e) => setEmail(e.target.value)}
115+
required
116+
placeholder="name@hospital.com"
117+
className="w-full mt-2 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:outline-none"
118+
/>
76119
</div>
77-
)}
78-
79-
{/* Button */}
80-
<button
81-
type="submit"
82-
disabled={loading}
83-
className="w-full rounded-md bg-blue-600 py-2 text-white font-medium hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
84-
>
85-
{loading ? 'Signing in…' : 'Sign in'}
86-
</button>
87-
</form>
88-
89-
{/* Footer */}
90-
<p className="mt-6 text-center text-sm text-gray-500">
91-
Don’t have an account?{' '}
92-
<Link
93-
href="/register"
94-
className="text-blue-600 hover:text-blue-700 font-medium hover:underline"
95-
>
96-
Register
97-
</Link>
98-
</p>
120+
121+
{/* Password */}
122+
<div>
123+
<div className="flex justify-between items-center">
124+
<label className="text-sm font-medium text-gray-700">
125+
Password
126+
</label>
127+
<Link
128+
href="/forgot-password"
129+
className="text-sm text-teal-600 hover:underline"
130+
>
131+
Forgot password?
132+
</Link>
133+
</div>
134+
135+
<div className="relative mt-2">
136+
<input
137+
type={showPassword ? 'text' : 'password'}
138+
value={password}
139+
onChange={(e) => setPassword(e.target.value)}
140+
required
141+
placeholder="Enter your password"
142+
className="w-full px-4 py-3 pr-12 border border-gray-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:outline-none"
143+
/>
144+
145+
<button
146+
type="button"
147+
onClick={() => setShowPassword(!showPassword)}
148+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition"
149+
>
150+
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
151+
</button>
152+
</div>
153+
</div>
154+
155+
{/* Error */}
156+
{error && (
157+
<div className="text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg p-3">
158+
{error}
159+
</div>
160+
)}
161+
162+
{/* Button */}
163+
<button
164+
type="submit"
165+
disabled={loading}
166+
className="w-full bg-teal-600 hover:bg-teal-700 text-white py-3 rounded-lg transition font-medium disabled:opacity-50"
167+
>
168+
{loading ? 'Signing in...' : 'Sign in'}
169+
</button>
170+
</form>
171+
172+
{/* Footer */}
173+
<p className="text-sm text-gray-500 text-center pt-4">
174+
Don’t have an account?{' '}
175+
<Link
176+
href="/register"
177+
className="text-teal-600 font-medium hover:underline"
178+
>
179+
Create account
180+
</Link>
181+
</p>
182+
</div>
183+
</div>
184+
185+
{/* RIGHT SIDE (IMAGE PANEL) */}
186+
<div className="hidden lg:block lg:w-1/2 relative">
187+
<Image
188+
src="/hospital.jpg"
189+
alt="Hospital"
190+
fill
191+
priority
192+
sizes="(max-width: 1024px) 0px, 50vw"
193+
className="object-cover"
194+
/>
195+
196+
{/* Overlay */}
197+
{/* Overlay */}
198+
<div className="absolute inset-0 flex items-end p-16">
199+
200+
{/* Bottom Fade Gradient */}
201+
<div className="absolute inset-0 bg-linear-to-t from-teal-900/90 via-teal-900/50 via-40% to-transparent" />
202+
203+
{/* Content */}
204+
<div className="relative text-white max-w-md space-y-4">
205+
<h2 className="text-3xl font-semibold leading-tight">
206+
Streamline your hospital operations
207+
</h2>
208+
<p className="text-sm text-white/80">
209+
A unified platform to manage patients, staff schedules,
210+
billing, and more — designed for modern healthcare teams.
211+
</p>
212+
213+
<div className="flex items-center gap-2 pt-4">
214+
<div className="flex -space-x-2">
215+
<div className="w-8 h-8 rounded-full bg-white/30 border border-white/50" />
216+
<div className="w-8 h-8 rounded-full bg-white/30 border border-white/50" />
217+
<div className="w-8 h-8 rounded-full bg-white/30 border border-white/50" />
218+
</div>
219+
<span className="text-sm text-white/80">
220+
Trusted by 2,000+ healthcare professionals
221+
</span>
222+
</div>
223+
</div>
224+
225+
</div>
99226
</div>
227+
100228
</div>
101229
);
102-
}
230+
}

0 commit comments

Comments
 (0)