Skip to content

Commit b63d041

Browse files
rootroot
authored andcommitted
please work
1 parent 04a91e6 commit b63d041

11 files changed

Lines changed: 387 additions & 55 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use client";
2+
import { useRouter } from "next/navigation";
3+
4+
export function LogoutButton() {
5+
const router = useRouter();
6+
function logout() {
7+
localStorage.removeItem("access");
8+
localStorage.removeItem("refresh");
9+
localStorage.removeItem("user_id");
10+
11+
router.push("/login");
12+
}
13+
14+
return (
15+
<button
16+
onClick={logout}
17+
className="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
18+
>
19+
Logout
20+
</button>
21+
);
22+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"use client";
2+
3+
import { LogoutButton } from "@/components/ui/logout";
4+
5+
export function Navbar() {
6+
return (
7+
<nav className="relative bg-indigo-800 after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-white/10">
8+
<div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
9+
<div className="relative flex h-16 items-center justify-between">
10+
<div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
11+
<div className="hidden sm:ml-6 sm:block">
12+
<div className="flex space-x-4">
13+
<a
14+
href="#"
15+
aria-current="page"
16+
className="rounded-md bg-gray-950/50 px-3 py-2 text-sm font-medium text-white"
17+
>
18+
Tasks
19+
</a>
20+
<a
21+
href="#"
22+
className="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
23+
>
24+
Timetable
25+
</a>
26+
<a
27+
href="#"
28+
className="rounded-md px-3 py-2 text-sm font-medium text-gray-300 hover:bg-white/5 hover:text-white"
29+
>
30+
Focus
31+
</a>
32+
<LogoutButton />
33+
</div>
34+
</div>
35+
</div>
36+
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0"></div>
37+
</div>
38+
</div>
39+
</nav>
40+
);
41+
}

client/src/pages/[id]/tasks.tsx

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,52 @@ interface Item {
2929

3030
export default function TasksPage() {
3131
const router = useRouter();
32-
const { id } = router.query;
32+
const [userId, setUserId] = useState<number | null>(null);
3333
const [items, setItems] = useState<Item[]>([]);
3434
const [loading, setLoading] = useState<boolean>(true);
3535

3636
useEffect(() => {
37-
if (!id) return;
38-
39-
async function fetchTasks() {
37+
async function fetchData() {
4038
try {
41-
// Remove the query bit when authentication is added.
42-
const response = await fetch(
43-
`http://localhost:8000/api/planner/tasks/?user_id=${id}`,
39+
const token = localStorage.getItem("access");
40+
if (!token) {
41+
router.push("/login"); //redirect to login if unauthorised
42+
return;
43+
}
44+
const auth = await fetch(
45+
"http://localhost:8000/api/planner/protected/",
46+
{
47+
headers: {
48+
Authorization: `Bearer ${token}`,
49+
},
50+
},
51+
);
52+
if (!auth.ok) {
53+
router.push("/login"); //redirect to login if unauthorised
54+
return;
55+
}
56+
57+
const user = await auth.json();
58+
setUserId(user.user_id);
59+
const tasksFetch = await fetch(
60+
`http://localhost:8000/api/planner/tasks/`,
61+
{
62+
headers: {
63+
Authorization: `Bearer ${token}`,
64+
},
65+
},
4466
);
45-
const data = await response.json();
67+
68+
const data = await tasksFetch.json();
4669
setItems(data);
4770
} catch (err) {
4871
console.error("Failed to load tasks:", err);
4972
} finally {
5073
setLoading(false);
5174
}
5275
}
53-
fetchTasks();
54-
}, [id]);
76+
fetchData();
77+
}, [router]);
5578

5679
if (loading) {
5780
return <p>Loading tasks...</p>;
@@ -99,7 +122,7 @@ export default function TasksPage() {
99122
</div>
100123
<div className="m-4 h-fit w-fit rounded-xl bg-zinc-700 p-4 text-center">
101124
<h1 className="mb-4 text-3xl font-bold text-zinc-300">Add Task</h1>
102-
<TaskForm userId={Number(id)} onTaskCreated={handleTaskCreated} />
125+
<TaskForm userId={Number(userId)} onTaskCreated={handleTaskCreated} />
103126
</div>
104127
</div>
105128
);

client/src/pages/_app.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ import "@/styles/globals.css";
33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
55
import type { AppProps } from "next/app";
6+
import { useRouter } from "next/router";
7+
8+
import { Navbar } from "@/components/ui/navbar";
69

710
const queryClient = new QueryClient();
811

912
export default function App({ Component, pageProps }: AppProps) {
13+
const router = useRouter();
14+
const noNavbarRoutes = ["/login", "/register", "/"];
15+
const showNavbar = !noNavbarRoutes.includes(router.pathname);
16+
1017
return (
1118
<QueryClientProvider client={queryClient}>
1219
<ReactQueryDevtools initialIsOpen={false} />
20+
{showNavbar && <Navbar />}
1321
<Component {...pageProps} />
1422
</QueryClientProvider>
1523
);

client/src/pages/index.tsx

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
1-
import { Inter as FontSans } from "next/font/google";
2-
import { useState } from "react";
3-
4-
import { usePings } from "@/hooks/pings";
5-
import { cn } from "@/lib/utils";
6-
7-
import { Button } from "../components/ui/button";
8-
9-
const fontSans = FontSans({
10-
subsets: ["latin"],
11-
variable: "--font-sans",
12-
});
13-
14-
export default function Home() {
15-
const [clicked, setClicked] = useState(false);
16-
const { data, isLoading } = usePings({
17-
enabled: clicked,
18-
});
1+
import { useRouter } from "next/navigation";
192

3+
export default function Landing() {
4+
const router = useRouter();
5+
function ToSignIn() {
6+
router.push("/register");
7+
}
208
return (
21-
<main
22-
className={cn(
23-
"flex min-h-screen flex-col items-center gap-4 p-24 font-sans",
24-
fontSans.variable,
25-
)}
26-
>
27-
<h1 className="text-3xl text-primary">wTest title</h1>
28-
<Button onClick={() => setClicked(true)}>
29-
{isLoading ? "Loading" : "Ping"}
30-
</Button>
31-
<p>
32-
Response from server: <span>{data as string}</span>
33-
</p>
34-
</main>
9+
<div className="flex min-h-screen flex-col justify-center bg-gray-900 px-6 py-12 lg:px-8">
10+
<button
11+
onClick={ToSignIn}
12+
className="flex w-full justify-center rounded-md bg-indigo-500 px-3 py-1.5 text-sm/6 font-semibold text-white hover:bg-indigo-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
13+
>
14+
Sign Up{" "}
15+
</button>
16+
</div>
3517
);
3618
}

client/src/pages/login.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use client";
2+
import { useRouter } from "next/navigation";
3+
4+
export default function Login() {
5+
const router = useRouter();
6+
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
7+
e.preventDefault();
8+
9+
const formData = new FormData(e.currentTarget);
10+
11+
const res = await fetch("http://127.0.0.1:8000/api/auth/login/", {
12+
method: "POST",
13+
headers: {
14+
"Content-Type": "application/json",
15+
},
16+
body: JSON.stringify({
17+
username: formData.get("username"),
18+
email: formData.get("email"),
19+
password: formData.get("password"),
20+
}),
21+
});
22+
23+
const data = await res.json();
24+
if (!res.ok) {
25+
alert("Invalid login");
26+
return;
27+
}
28+
29+
localStorage.setItem("access", data.access);
30+
localStorage.setItem("refresh", data.refresh);
31+
32+
const token = localStorage.getItem("access");
33+
34+
const userFetch = await fetch(
35+
"http://127.0.0.1:8000/api/planner/protected",
36+
{
37+
headers: {
38+
Authorization: `Bearer ${token}`,
39+
},
40+
},
41+
);
42+
43+
const user = await userFetch.json();
44+
router.push(`/${user.user_id}/tasks`);
45+
}
46+
47+
return (
48+
<div className="flex min-h-screen flex-col justify-center bg-gray-900 px-6 py-12 lg:px-8">
49+
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
50+
<h2 className="mt-10 text-center text-2xl/9 font-bold tracking-tight text-white">
51+
Log in to your account
52+
</h2>
53+
</div>
54+
55+
<div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
56+
<form onSubmit={handleSubmit} className="space-y-6">
57+
<label className="block text-sm/6 font-medium text-gray-100">
58+
Username
59+
</label>
60+
<div className="mt-2">
61+
<input
62+
id="username"
63+
type="text"
64+
name="username"
65+
required
66+
autoComplete="username"
67+
className="block w-full rounded-md bg-white/5 px-3 py-1.5 text-base text-white outline-1 -outline-offset-1 outline-white/10 placeholder:text-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-500 sm:text-sm/6"
68+
/>
69+
</div>
70+
71+
<label className="block text-sm/6 font-medium text-gray-100">
72+
Email
73+
</label>
74+
<div className="mt-2">
75+
<input
76+
id="email"
77+
type="email"
78+
name="email"
79+
required
80+
autoComplete="email"
81+
className="block w-full rounded-md bg-white/5 px-3 py-1.5 text-base text-white outline-1 -outline-offset-1 outline-white/10 placeholder:text-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-500 sm:text-sm/6"
82+
/>
83+
</div>
84+
85+
<div>
86+
<div className="flex items-center justify-between">
87+
<label className="block text-sm/6 font-medium text-gray-100">
88+
Password
89+
</label>
90+
<div className="text-sm">
91+
<a className="font-semibold text-indigo-400 hover:text-indigo-300">
92+
Forgot password?
93+
</a>
94+
</div>
95+
</div>
96+
<div className="mt-2">
97+
<input
98+
id="password"
99+
type="password"
100+
name="password"
101+
required
102+
className="block w-full rounded-md bg-white/5 px-3 py-1.5 text-base text-white outline-1 -outline-offset-1 outline-white/10 placeholder:text-gray-500 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-500 sm:text-sm/6"
103+
/>
104+
</div>
105+
</div>
106+
107+
<div>
108+
<button
109+
type="submit"
110+
className="flex w-full justify-center rounded-md bg-indigo-500 px-3 py-1.5 text-sm/6 font-semibold text-white hover:bg-indigo-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
111+
>
112+
Sign in
113+
</button>
114+
</div>
115+
</form>
116+
</div>
117+
</div>
118+
);
119+
}

0 commit comments

Comments
 (0)