Skip to content

Commit 3e03be6

Browse files
committed
Introduce PageLayout to have less duplication of Headers
1 parent 7dfd3d0 commit 3e03be6

File tree

5 files changed

+140
-125
lines changed

5 files changed

+140
-125
lines changed

ui/src/App.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,27 @@ import {createBrowserRouter, RouterProvider} from 'react-router'
33
import HomePage from './pages/HomePage'
44
import Login from './pages/Login'
55
import type {User} from './types/User';
6+
import Layout from "./components/PageLayout";
67

78
function App() {
89
const [user, setUser] = useState<User | null>(null);
910

1011
const router = createBrowserRouter([
11-
{
12-
path: '/',
13-
element: <HomePage user={user} setUser={setUser}/>
14-
},
15-
{
16-
path: '/login',
17-
element: <Login user={user} setUser={setUser}/>
18-
},
19-
]);
12+
{
13+
element: <Layout user={user} setUser={setUser}/>,
14+
children: [
15+
{
16+
path: '/',
17+
element: <HomePage user={user} setUser={setUser}/>
18+
},
19+
{
20+
path: '/login',
21+
element: <Login user={user} setUser={setUser}/>
22+
}]
23+
}
24+
25+
])
26+
;
2027

2128
useEffect(() => {
2229
// Try to load user from localStorage on app start

ui/src/components/Header.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ function Header({user, setUser}: {
3333
<div>
3434
{user ? (
3535
<div className="flex items-center gap-4">
36-
<span>
37-
{user.firstName} {user.lastName} ({user.role})
38-
</span>
36+
<span>
37+
{user.firstName} {user.lastName} ({user.role})
38+
</span>
3939
<button
4040
onClick={logout}
4141
className="bg-blue-600 text-white px-5 py-2 rounded-full font-semibold shadow hover:bg-blue-700 transition"
@@ -44,13 +44,14 @@ function Header({user, setUser}: {
4444
</button>
4545
</div>
4646
) : (
47-
<Link
48-
to="/login"
49-
className="bg-blue-600 text-white px-5 py-2 rounded-full font-semibold shadow hover:bg-blue-700 transition"
50-
>
51-
Log In
52-
</Link>
53-
47+
<div className="flex items-center gap-4">
48+
<Link
49+
to="/login"
50+
className="bg-blue-600 text-white px-5 py-2 rounded-full font-semibold shadow hover:bg-blue-700 transition"
51+
>
52+
Log In
53+
</Link>
54+
</div>
5455
)}
5556
</div>
5657
</header>

ui/src/components/PageLayout.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {Outlet} from "react-router";
2+
import Header from "./Header";
3+
import {User} from "../types/User";
4+
5+
function Layout({user, setUser}: { user: User | null, setUser: (user: User | null) => void }) {
6+
return (
7+
<div className="min-h-screen bg-gray-50">
8+
<Header user={user} setUser={setUser}/>
9+
<main>
10+
<Outlet/>
11+
</main>
12+
</div>
13+
)
14+
}
15+
16+
export default Layout

ui/src/pages/HomePage.tsx

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import {useEffect, useState} from 'react'
22
import '../App.css'
33
import type {Course} from '../types/Course';
4-
import {User} from "../types/User";
5-
import Header from '../components/Header'
64
import {useConfig} from '../ConfigContext';
75

8-
function HomePage({user, setUser}: { user: User | null, setUser: (user: User | null) => void }) {
6+
function HomePage() {
97
const [courses, setCourses] = useState<Course[]>([]);
10-
const { backendUrl } = useConfig();
8+
const {backendUrl} = useConfig();
119

1210
useEffect(() => {
1311
fetch(`${backendUrl}/courses`)
@@ -17,41 +15,38 @@ function HomePage({user, setUser}: { user: User | null, setUser: (user: User | n
1715
}, [])
1816

1917
return (
20-
<div className="min-h-screen bg-gray-50">
21-
<Header user={user} setUser={setUser}/>
22-
<div className="max-w-5xl mx-auto mt-10 pb-5">
23-
<h1 className="text-4xl font-extrabold mb-8 text-gray-800">All Courses</h1>
24-
<div className="overflow-x-auto">
25-
<table className="table-auto min-w-full bg-white shadow-xl border border-gray-100">
26-
<thead>
27-
<tr>
28-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50 rounded-tl-2xl">Name</th>
29-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Description</th>
30-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Instructor</th>
31-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Start Date</th>
32-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">End Date</th>
33-
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50 rounded-tr-2xl">Max
34-
Students
35-
</th>
18+
<div className="max-w-5xl mx-auto mt-10 pb-5">
19+
<h1 className="text-4xl font-extrabold mb-8 text-gray-800">All Courses</h1>
20+
<div className="overflow-x-auto">
21+
<table className="table-auto min-w-full bg-white shadow-xl border border-gray-100">
22+
<thead>
23+
<tr>
24+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50 rounded-tl-2xl">Name</th>
25+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Description</th>
26+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Instructor</th>
27+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">Start Date</th>
28+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50">End Date</th>
29+
<th className="px-6 py-4 text-left font-semibold text-gray-600 bg-gray-50 rounded-tr-2xl">Max
30+
Students
31+
</th>
32+
</tr>
33+
</thead>
34+
<tbody>
35+
{courses.map((course, idx) => (
36+
<tr
37+
key={course.id}
38+
className={`border-t transition ${idx % 2 === 0 ? 'bg-gray-50' : 'bg-white'} hover:bg-blue-50`}
39+
>
40+
<td className="px-6 py-4 text-gray-700">{course.name}</td>
41+
<td className="px-6 py-4 text-gray-700">{course.description}</td>
42+
<td className="px-6 py-4 text-gray-700">{course.instructor}</td>
43+
<td className="px-6 py-4 text-gray-700">{course.startDate}</td>
44+
<td className="px-6 py-4 text-gray-700">{course.endDate}</td>
45+
<td className="px-6 py-4 text-gray-700">{course.maxEnrollments}</td>
3646
</tr>
37-
</thead>
38-
<tbody>
39-
{courses.map((course, idx) => (
40-
<tr
41-
key={course.id}
42-
className={`border-t transition ${idx % 2 === 0 ? 'bg-gray-50' : 'bg-white'} hover:bg-blue-50`}
43-
>
44-
<td className="px-6 py-4 text-gray-700">{course.name}</td>
45-
<td className="px-6 py-4 text-gray-700">{course.description}</td>
46-
<td className="px-6 py-4 text-gray-700">{course.instructor}</td>
47-
<td className="px-6 py-4 text-gray-700">{course.startDate}</td>
48-
<td className="px-6 py-4 text-gray-700">{course.endDate}</td>
49-
<td className="px-6 py-4 text-gray-700">{course.maxEnrollments}</td>
50-
</tr>
51-
))}
52-
</tbody>
53-
</table>
54-
</div>
47+
))}
48+
</tbody>
49+
</table>
5550
</div>
5651
</div>
5752
);

ui/src/pages/Login.tsx

Lines changed: 64 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import {Link, useNavigate} from "react-router";
33
import {useMutation} from "@tanstack/react-query";
44
import React, {useState} from "react";
55
import type {User} from "../types/User";
6-
import Header from "../components/Header";
7-
import { useConfig } from '../ConfigContext';
6+
import {useConfig} from '../ConfigContext';
87

9-
async function loginRequest(credentials: { emailAddress: string; password: string }) {
10-
const { backendUrl } = useConfig();
8+
async function loginRequest(credentials: { emailAddress: string; password: string }, backendUrl: string) {
119
try {
1210
const response = await fetch(`${backendUrl}/login`, {
1311
method: "POST",
@@ -41,13 +39,14 @@ async function loginRequest(credentials: { emailAddress: string; password: strin
4139
}
4240
}
4341

44-
export default function Login({user, setUser}: { user: User | null, setUser: (user: User | null) => void }) {
42+
export default function Login({setUser}: { user: User | null, setUser: (user: User | null) => void }) {
4543
const [emailAddress, setEmailAddress] = useState("");
4644
const [password, setPassword] = useState("");
45+
const {backendUrl} = useConfig();
4746
const navigate = useNavigate();
4847

4948
const {mutate, isPending, error} = useMutation({
50-
mutationFn: loginRequest,
49+
mutationFn: (creds: { emailAddress: string; password: string }) => loginRequest(creds, backendUrl),
5150
onSuccess: (userData) => {
5251
localStorage.setItem("user", JSON.stringify(userData));
5352
setUser(userData);
@@ -61,71 +60,68 @@ export default function Login({user, setUser}: { user: User | null, setUser: (us
6160
};
6261

6362
return (
64-
<div className="min-h-screen bg-gray-50">
65-
<Header user={user} setUser={setUser}/>
66-
<div className="flex justify-center items-center mt-16">
67-
<div className="bg-white shadow-lg rounded-xl p-8 w-full max-w-md">
68-
<h2 className="text-2xl font-bold text-center text-blue-600 mb-6">
69-
Log In
70-
</h2>
71-
<form className="space-y-5" onSubmit={handleSubmit}>
72-
<div>
73-
<label
74-
className="block text-gray-700 font-medium mb-2"
75-
htmlFor="email"
76-
>
77-
Email
78-
</label>
79-
<input
80-
id="email"
81-
type="email"
82-
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
83-
placeholder="Enter your email"
84-
required
85-
value={emailAddress}
86-
onChange={(e) => setEmailAddress(e.target.value)}
87-
/>
88-
</div>
89-
<div>
90-
<label
91-
className="block text-gray-700 font-medium mb-2"
92-
htmlFor="password"
93-
>
94-
Password
95-
</label>
96-
<input
97-
id="password"
98-
type="password"
99-
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
100-
placeholder="Enter your password"
101-
required
102-
value={password}
103-
onChange={(e) => setPassword(e.target.value)}
104-
/>
105-
</div>
106-
{error && (
107-
<div className="text-red-600 text-center">
108-
{(error as Error).message}
109-
</div>
110-
)}
111-
<button
112-
type="submit"
113-
disabled={isPending}
114-
className="w-full bg-blue-600 text-white py-2 rounded-lg font-semibold
115-
hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
63+
<div className="flex justify-center items-center mt-16">
64+
<div className="bg-white shadow-lg rounded-xl p-8 w-full max-w-md">
65+
<h2 className="text-2xl font-bold text-center text-blue-600 mb-6">
66+
Log In
67+
</h2>
68+
<form className="space-y-5" onSubmit={handleSubmit}>
69+
<div>
70+
<label
71+
className="block text-gray-700 font-medium mb-2"
72+
htmlFor="email"
11673
>
117-
{isPending ? "Logging in..." : "Log In"}
118-
</button>
119-
</form>
120-
<div className="text-center mt-6">
121-
<span className="text-gray-600">Don't have an account?</span>
122-
<Link
123-
to="/register"
124-
className="text-blue-600 font-medium ml-2 hover:underline"
74+
Email
75+
</label>
76+
<input
77+
id="email"
78+
type="email"
79+
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
80+
placeholder="Enter your email"
81+
required
82+
value={emailAddress}
83+
onChange={(e) => setEmailAddress(e.target.value)}
84+
/>
85+
</div>
86+
<div>
87+
<label
88+
className="block text-gray-700 font-medium mb-2"
89+
htmlFor="password"
12590
>
126-
Register
127-
</Link>
91+
Password
92+
</label>
93+
<input
94+
id="password"
95+
type="password"
96+
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
97+
placeholder="Enter your password"
98+
required
99+
value={password}
100+
onChange={(e) => setPassword(e.target.value)}
101+
/>
128102
</div>
103+
{error && (
104+
<div className="text-red-600 text-center">
105+
{(error as Error).message}
106+
</div>
107+
)}
108+
<button
109+
type="submit"
110+
disabled={isPending}
111+
className="w-full bg-blue-600 text-white py-2 rounded-lg font-semibold
112+
hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed"
113+
>
114+
{isPending ? "Logging in..." : "Log In"}
115+
</button>
116+
</form>
117+
<div className="text-center mt-6">
118+
<span className="text-gray-600">Don't have an account?</span>
119+
<Link
120+
to="/register"
121+
className="text-blue-600 font-medium ml-2 hover:underline"
122+
>
123+
Register
124+
</Link>
129125
</div>
130126
</div>
131127
</div>

0 commit comments

Comments
 (0)