Skip to content

Commit 63f4a4d

Browse files
authored
Merge pull request #16 from atlp-rwanda/ft-appointments-dashboard
Feature: appointments dashboard
2 parents facdc6e + 33156fe commit 63f4a4d

36 files changed

+2785
-1147
lines changed

Diff for: .env

-2
This file was deleted.

Diff for: .github/workflows/lint.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ jobs:
2626
- name: Install Node.js dependencies
2727
run: npm ci
2828

29-
- name: Run linter
30-
run: npm run lint
29+
# Build step runs a linter as well and will fail if there are any linting errors
30+
- name: Run Linter and Build
31+
run: npm run build

Diff for: .gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ yarn-debug.log*
2626
yarn-error.log*
2727

2828
# local env files
29-
.env*.local
29+
.env*
3030

3131
# vercel
3232
.vercel

Diff for: app/auth/actions.ts

+7
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,10 @@ export async function signup(formData: FormData) {
129129
revalidatePath("/dashboard");
130130
redirect("/dashboard");
131131
}
132+
133+
export async function logout() {
134+
const supabase = createClient();
135+
await supabase.auth.signOut();
136+
revalidatePath("/");
137+
redirect("/");
138+
}

Diff for: app/auth/newpassword/page.tsx

+33-26
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,51 @@
11
"use client";
2-
import Link from "next/link";
3-
import { useState } from "react";
4-
import { login } from "../actions";
5-
import { useRouter } from "next/navigation";
62
import { createClient } from "@/utils/supabase/client";
3+
import { useRouter } from "next/navigation";
4+
import { useState } from "react";
75

8-
const supabase = createClient();
96
export default function NewPassword() {
10-
const [message, setMessage] = useState<{
11-
type: string;
12-
text: string;
13-
} | null>(null);
7+
const [message, setMessage] = useState<{
8+
type: string;
9+
text: string;
10+
} | null>(null);
1411
const [pending, setPending] = useState(false);
1512
const [newpassword, setNewpassword] = useState("");
1613
const [confirmpassword, setConfirmPassword] = useState("");
1714
const router = useRouter();
1815
const handlePasswordReset = async () => {
19-
setPending(true)
16+
setPending(true);
2017
if (newpassword !== confirmpassword) {
2118
setMessage({ type: "error", text: "Passwords do not match" });
2219
setPending(false);
2320
return;
2421
}
22+
23+
const supabase = createClient();
2524
const { error } = await supabase.auth.updateUser({ password: newpassword });
2625
if (error) {
2726
console.log(error);
2827
setPending(false);
29-
}
30-
else {
28+
} else {
3129
setMessage({ type: "success", text: "Passwords was reset successfully" });
3230
setTimeout(() => {
33-
router.push("/dashboard")
34-
}, 1000)
35-
31+
router.push("/dashboard");
32+
}, 1000);
3633
}
37-
}
34+
};
3835
return (
3936
<div className="flex flex-row border border-primary-500 bg-primary-200 rounded-2xl max-w-[650px] mx-auto">
40-
<form
41-
className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6"
42-
>
43-
<p className="text-primary-400 text-lg font-bold mb-3">Create new password!</p>
44-
{message?.type==="error" && (<p aria-live="polite" className="mb-3 text-red-600 text-center font-semibold">
45-
{message.text}
46-
</p>)}
37+
<form className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6">
38+
<p className="text-primary-400 text-lg font-bold mb-3">
39+
Create new password!
40+
</p>
41+
{message?.type === "error" && (
42+
<p
43+
aria-live="polite"
44+
className="mb-3 text-red-600 text-center font-semibold"
45+
>
46+
{message.text}
47+
</p>
48+
)}
4749

4850
<div className="flex flex-col mb-5">
4951
<label htmlFor="password" className="mb-1">
@@ -83,9 +85,14 @@ export default function NewPassword() {
8385
>
8486
{pending ? "Loading..." : "Next"}
8587
</button>
86-
{message?.type==="success" && (<p aria-live="polite" className="mt §-3 text-[#4BB543] text-center font-semibold">
87-
{message.text}
88-
</p>)}
88+
{message?.type === "success" && (
89+
<p
90+
aria-live="polite"
91+
className="mt §-3 text-[#4BB543] text-center font-semibold"
92+
>
93+
{message.text}
94+
</p>
95+
)}
8996
</form>
9097
</div>
9198
);

Diff for: app/auth/reset/page.tsx

+34-37
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,37 @@
11
"use client";
2+
import { createClient } from "@/utils/supabase/client";
23
import Link from "next/link";
3-
import { useState } from "react";
4-
import { login } from "../actions";
54
import { useRouter } from "next/navigation";
6-
import { createClient } from "@/utils/supabase/client";
5+
import { useState } from "react";
76

8-
const supabase = createClient();
97
export default function Reset() {
108
const [error, setError] = useState("");
119
const [pending, setPending] = useState(false);
12-
const [email, setEmail]=useState("");
13-
const router= useRouter();
10+
const [email, setEmail] = useState("");
11+
const router = useRouter();
1412

15-
const handelReset=async()=>{
16-
setPending(true);
17-
const {data, error}= await supabase.auth.resetPasswordForEmail(email)
18-
if(error){
19-
setError("you did not enter an email");
20-
setPending(false);
21-
}
22-
else{
23-
alert("a reset otp was sent to your email");
24-
router.push(`/auth/verify?email=${encodeURIComponent(email)}`);
25-
}
26-
setPending(false)
27-
}
13+
const handelReset = async () => {
14+
setPending(true);
15+
const supabase = createClient();
16+
const { data, error } = await supabase.auth.resetPasswordForEmail(email);
17+
if (error) {
18+
setError("you did not enter an email");
19+
setPending(false);
20+
} else {
21+
alert("a reset otp was sent to your email");
22+
router.push(`/auth/verify?email=${encodeURIComponent(email)}`);
23+
}
24+
setPending(false);
25+
};
2826

2927
return (
3028
<div className="flex flex-row border border-primary-500 bg-primary-200 rounded-2xl max-w-[650px] mx-auto">
31-
<form
32-
className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6"
33-
>
34-
<p className="text-primary-400 text-lg font-bold mb-3">Forgot Password?</p>
29+
<form className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6">
30+
<p className="text-primary-400 text-lg font-bold mb-3">
31+
Forgot Password?
32+
</p>
3533
<p className="mb-3">Enter your email and we’ll send you instructions</p>
3634

37-
3835
<div className="flex flex-col mb-5">
3936
<label htmlFor="email" className="mb-1">
4037
User Email:
@@ -47,29 +44,29 @@ setPending(false)
4744
autoFocus
4845
autoComplete="email"
4946
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none"
50-
value={email}
51-
onChange={(e) => setEmail(e.target.value)}
47+
value={email}
48+
onChange={(e) => setEmail(e.target.value)}
5249
/>
5350
</div>
5451
<button
5552
className="bg-primary-700 text-white px-5 py-2 rounded-lg mt-5 disabled:bg-primary-400 disabled:cursor-not-allowed"
56-
onClick={handelReset}
57-
disabled={pending}>
58-
{pending?"Loading":"Send Reset OTP"}
53+
onClick={handelReset}
54+
disabled={pending}
55+
>
56+
{pending ? "Loading" : "Send Reset OTP"}
5957
</button>
60-
<p aria-live="polite" className="mb-3 text-red-600 text-center font-semibold">
58+
<p
59+
aria-live="polite"
60+
className="mb-3 text-red-600 text-center font-semibold"
61+
>
6162
{error}
6263
</p>
6364
<div className="text-center mt-4">
64-
<Link
65-
href="/auth/login"
66-
className="px-4 py-1.5 rounded-lg"
67-
>
68-
Back to login
69-
</Link>
65+
<Link href="/auth/login" className="px-4 py-1.5 rounded-lg">
66+
Back to login
67+
</Link>
7068
</div>
7169
</form>
7270
</div>
73-
7471
);
7572
}

Diff for: app/auth/verify/page.tsx

+59-40
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,54 @@
11
"use client";
2-
import Link from "next/link";
3-
import { useState } from "react";
4-
import { login } from "../actions";
5-
import { useRouter, useSearchParams } from "next/navigation";
62
import { createClient } from "@/utils/supabase/client";
3+
import { useRouter, useSearchParams } from "next/navigation";
4+
import { Suspense, useState } from "react";
75

8-
const supabase = createClient();
9-
export default function Reset() {
6+
function Reset() {
7+
const router = useRouter();
8+
const searchParams = useSearchParams();
109
const [error, setError] = useState("");
1110
const [code, setCode] = useState("");
1211
const [pending, setPending] = useState(false);
13-
const [message, setMessage]=useState<{
12+
const [message, setMessage] = useState<{
1413
type: string;
1514
text: string;
16-
} | null>(null);
17-
const router= useRouter();
18-
const searchParams = useSearchParams();
19-
const email = searchParams.get('email');
20-
const handelReset=async()=>{
21-
setPending(true);
22-
const otpCode=code;
23-
const {data, error} =await supabase.auth.verifyOtp({email:email, token:otpCode, type:"email"})
24-
if(error){
25-
setMessage({type:"error", text:"invalid otp"});
26-
}
27-
else{
28-
setMessage({type:"sucess", text:"Valid Otp provided"});
29-
setTimeout(()=>{
15+
} | null>(null);
16+
const email = searchParams.get("email");
17+
18+
const handelReset = async () => {
19+
setPending(true);
20+
const supabase = createClient();
21+
const otpCode = code;
22+
const { data, error } = await supabase.auth.verifyOtp({
23+
email: email!,
24+
token: otpCode,
25+
type: "email",
26+
});
27+
28+
if (error) {
29+
setMessage({ type: "error", text: "invalid otp" });
30+
} else {
31+
setMessage({ type: "sucess", text: "Valid Otp provided" });
32+
setTimeout(() => {
3033
router.push("/auth/newpassword");
31-
}, 1000)
32-
}
33-
setPending(false)
34-
}
34+
}, 1000);
35+
}
36+
setPending(false);
37+
};
3538

3639
return (
3740
<div className="flex flex-row border border-primary-500 bg-primary-200 rounded-2xl max-w-[650px] mx-auto">
38-
<form
39-
className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6"
40-
>
41+
<form className="flex flex-col flex-1 border-r border-primary-500 rounded-2xl bg-white p-6">
4142
<p className="text-primary-400 text-lg font-bold mb-3">Verify OTP</p>
4243
<p className="mb-3">Enter the otp you received on your email </p>
43-
{message?.type ==="error" &&(<p aria-live="polite" className="mb-3 text-red-600 text-center font-semibold">
44-
{message.text}
45-
</p>)}
44+
{message?.type === "error" && (
45+
<p
46+
aria-live="polite"
47+
className="mb-3 text-red-600 text-center font-semibold"
48+
>
49+
{message.text}
50+
</p>
51+
)}
4652

4753
<div className="flex flex-col mb-5">
4854
<label htmlFor="otp" className="mb-1">
@@ -56,21 +62,34 @@ else{
5662
autoFocus
5763
autoComplete="off"
5864
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none"
59-
value={code}
60-
onChange={(e) => setCode(e.target.value)}
65+
value={code}
66+
onChange={(e) => setCode(e.target.value)}
6167
/>
6268
</div>
6369
<button
6470
className="bg-primary-700 text-white px-5 py-2 rounded-lg mt-5 disabled:bg-primary-400 disabled:cursor-not-allowed"
65-
onClick={handelReset}
66-
disabled={pending}>
67-
{pending?"Verifying...":"Verify"}
71+
onClick={handelReset}
72+
disabled={pending}
73+
>
74+
{pending ? "Verifying..." : "Verify"}
6875
</button>
69-
{message?.type ==="success" &&(<p aria-live="polite" className="mt-3 text-[#4BB543] text-center font-semibold">
70-
{message.text}
71-
</p>)}
76+
{message?.type === "success" && (
77+
<p
78+
aria-live="polite"
79+
className="mt-3 text-[#4BB543] text-center font-semibold"
80+
>
81+
{message.text}
82+
</p>
83+
)}
7284
</form>
7385
</div>
74-
86+
);
87+
}
88+
89+
export default function ResetPage() {
90+
return (
91+
<Suspense>
92+
<Reset />
93+
</Suspense>
7594
);
7695
}

0 commit comments

Comments
 (0)