Skip to content

Commit facdc6e

Browse files
authored
Merge pull request #15 from atlp-rwanda/ft-profile-settings
Feature: Profile settings
2 parents bc27ac3 + 26c82dd commit facdc6e

File tree

15 files changed

+802
-27
lines changed

15 files changed

+802
-27
lines changed

Diff for: .env

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
NEXT_PUBLIC_SUPABASE_URL=https://vaxjykeihsernxqxeyiy.supabase.co
2+
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZheGp5a2VpaHNlcm54cXhleWl5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTc0NDAzMTksImV4cCI6MjAzMzAxNjMxOX0.PbKiCmDG-U_1H0gkelN3_ze8_VE8lqIsc3pi5IH2bBA

Diff for: app/auth/actions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export async function signup(formData: FormData) {
115115
const { error: spbsError } = await supabase.from("doctor").insert({
116116
name: data.fullname,
117117
role: data.position,
118+
email: data.email,
118119
hospital: data.hospital,
119120
certificate: certificateURL.publicUrl,
120121
cv: cvURL.publicUrl,

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

+4
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,12 @@ export default function LoginPage() {
5454
/>
5555
</div>
5656
<div className="flex flex-col mb-5">
57+
<div className="flex justify-between">
5758
<label htmlFor="password" className="mb-1">
5859
Password:
5960
</label>
61+
<Link href="/auth/reset" className="text-[#246BFD] font-medium">Forgot password?</Link>
62+
</div>
6063
<input
6164
id="password"
6265
name="password"
@@ -65,6 +68,7 @@ export default function LoginPage() {
6568
autoComplete="current-password"
6669
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none font-serif"
6770
/>
71+
6872
</div>
6973
<div className="flex flex-row mb-5 gap-1 items-center">
7074
<input type="checkbox" id="remember" name="remember" />

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

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"use client";
2+
import Link from "next/link";
3+
import { useState } from "react";
4+
import { login } from "../actions";
5+
import { useRouter } from "next/navigation";
6+
import { createClient } from "@/utils/supabase/client";
7+
8+
const supabase = createClient();
9+
export default function NewPassword() {
10+
const [message, setMessage] = useState<{
11+
type: string;
12+
text: string;
13+
} | null>(null);
14+
const [pending, setPending] = useState(false);
15+
const [newpassword, setNewpassword] = useState("");
16+
const [confirmpassword, setConfirmPassword] = useState("");
17+
const router = useRouter();
18+
const handlePasswordReset = async () => {
19+
setPending(true)
20+
if (newpassword !== confirmpassword) {
21+
setMessage({ type: "error", text: "Passwords do not match" });
22+
setPending(false);
23+
return;
24+
}
25+
const { error } = await supabase.auth.updateUser({ password: newpassword });
26+
if (error) {
27+
console.log(error);
28+
setPending(false);
29+
}
30+
else {
31+
setMessage({ type: "success", text: "Passwords was reset successfully" });
32+
setTimeout(() => {
33+
router.push("/dashboard")
34+
}, 1000)
35+
36+
}
37+
}
38+
return (
39+
<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>)}
47+
48+
<div className="flex flex-col mb-5">
49+
<label htmlFor="password" className="mb-1">
50+
New Password:
51+
</label>
52+
<input
53+
id="newpassword"
54+
name="newpassword"
55+
type="password"
56+
required
57+
autoFocus
58+
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none font-serif"
59+
value={newpassword}
60+
onChange={(e) => setNewpassword(e.target.value)}
61+
/>
62+
</div>
63+
<div className="flex flex-col mb-5">
64+
<div className="flex justify-between">
65+
<label htmlFor="password" className="mb-1">
66+
Confirm Password:
67+
</label>
68+
</div>
69+
<input
70+
id="confirmpassword"
71+
name="confirmpassword"
72+
type="password"
73+
required
74+
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none font-serif"
75+
value={confirmpassword}
76+
onChange={(e) => setConfirmPassword(e.target.value)}
77+
/>
78+
</div>
79+
<button
80+
className="bg-primary-700 text-white px-5 py-2 rounded-lg mt-5 disabled:bg-primary-400 disabled:cursor-not-allowed"
81+
disabled={pending}
82+
onClick={handlePasswordReset}
83+
>
84+
{pending ? "Loading..." : "Next"}
85+
</button>
86+
{message?.type==="success" && (<p aria-live="polite" className="mt §-3 text-[#4BB543] text-center font-semibold">
87+
{message.text}
88+
</p>)}
89+
</form>
90+
</div>
91+
);
92+
}

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

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use client";
2+
import Link from "next/link";
3+
import { useState } from "react";
4+
import { login } from "../actions";
5+
import { useRouter } from "next/navigation";
6+
import { createClient } from "@/utils/supabase/client";
7+
8+
const supabase = createClient();
9+
export default function Reset() {
10+
const [error, setError] = useState("");
11+
const [pending, setPending] = useState(false);
12+
const [email, setEmail]=useState("");
13+
const router= useRouter();
14+
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+
}
28+
29+
return (
30+
<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>
35+
<p className="mb-3">Enter your email and we’ll send you instructions</p>
36+
37+
38+
<div className="flex flex-col mb-5">
39+
<label htmlFor="email" className="mb-1">
40+
User Email:
41+
</label>
42+
<input
43+
id="email"
44+
name="email"
45+
type="email"
46+
required
47+
autoFocus
48+
autoComplete="email"
49+
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)}
52+
/>
53+
</div>
54+
<button
55+
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"}
59+
</button>
60+
<p aria-live="polite" className="mb-3 text-red-600 text-center font-semibold">
61+
{error}
62+
</p>
63+
<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>
70+
</div>
71+
</form>
72+
</div>
73+
74+
);
75+
}

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

+29-12
Original file line numberDiff line numberDiff line change
@@ -144,32 +144,49 @@ export default function SignupPage() {
144144
</select>
145145
</div>
146146
<div className="flex flex-col mb-5">
147-
<label htmlFor="email" className="mb-1">
148-
Position:
147+
<label htmlFor="position" className="mb-1">
148+
Specialization:
149149
</label>
150-
<input
150+
<select
151+
defaultValue=""
151152
id="position"
152153
name="position"
153-
type="text"
154154
required
155155
autoComplete="position"
156156
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none"
157-
/>
157+
>
158+
<option disabled value="">Select Specialization</option>
159+
<option value="Dentist">Dentist</option>
160+
<option value="Dermatologist">Dermatologist</option>
161+
<option value="Nutritionist">Nutritionist</option>
162+
<option value="Cardiologist">Cardiologist</option>
163+
<option value="Neurologist">Neurologist</option>
164+
<option value="Pediatrician">Pediatrician</option>
165+
<option value="Surgeon">Surgeon</option>
166+
<option value="Immunologist">Immunologist</option>
167+
<option value="Gastroenterologist">Gastroenterologist</option>
168+
</select>
158169
</div>
159170
<div className="flex flex-col mb-5">
160-
<label htmlFor="email" className="mb-1">
161-
Hospital
171+
<label htmlFor="hospital" className="mb-1">
172+
Hospital:
162173
</label>
163-
<input
174+
<select
175+
defaultValue=""
164176
id="hospital"
165177
name="hospital"
166-
type="text"
167178
required
168-
autoComplete="hospital"
169179
className="p-2 border rounded-lg border-neutral-400 focus:border-primary-500 focus:ring-2 outline-none"
170-
/>
180+
>
181+
<option disabled value="">Select a hospital</option>
182+
<option value="CHUK">CHUK</option>
183+
<option value="CHUB">CHUB</option>
184+
<option value="MASAKA Hospital">MASAKA Hospital</option>
185+
<option value="King FaisaL Hospital">King FaisaL Hospital</option>
186+
<option value="KIBAGABAGA HOSPITAL ">KIBAGABAGA HOSPITAL </option>
187+
<option value="Muhima HOSPITAL ">Muhima HOSPITAL </option>
188+
</select>
171189
</div>
172-
173190
<div className="flex flex-col mb-5">
174191
<label htmlFor="password" className="mb-1">
175192
Password:

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

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"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";
6+
import { createClient } from "@/utils/supabase/client";
7+
8+
const supabase = createClient();
9+
export default function Reset() {
10+
const [error, setError] = useState("");
11+
const [code, setCode] = useState("");
12+
const [pending, setPending] = useState(false);
13+
const [message, setMessage]=useState<{
14+
type: string;
15+
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(()=>{
30+
router.push("/auth/newpassword");
31+
}, 1000)
32+
}
33+
setPending(false)
34+
}
35+
36+
return (
37+
<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+
<p className="text-primary-400 text-lg font-bold mb-3">Verify OTP</p>
42+
<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>)}
46+
47+
<div className="flex flex-col mb-5">
48+
<label htmlFor="otp" className="mb-1">
49+
OTP:
50+
</label>
51+
<input
52+
id="otp"
53+
name="otp"
54+
type="text"
55+
required
56+
autoFocus
57+
autoComplete="off"
58+
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)}
61+
/>
62+
</div>
63+
<button
64+
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"}
68+
</button>
69+
{message?.type ==="success" &&(<p aria-live="polite" className="mt-3 text-[#4BB543] text-center font-semibold">
70+
{message.text}
71+
</p>)}
72+
</form>
73+
</div>
74+
75+
);
76+
}

Diff for: app/profile/page.tsx

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
'use client'
3+
import {useState, useEffect} from "react";
4+
import Navbar from "../../components/navbar";
5+
import TopBar from "../../components/topbar";
6+
import { DoctorType } from "@/app/dashboard/page"
7+
import ProfileSection from "../../components/profileSettings";
8+
import { createClient } from "@/utils/supabase/client";
9+
const supabase = createClient();
10+
export default function Profile(){
11+
const [doctorData, setdoctorData] = useState<DoctorType | undefined>(undefined);
12+
const [userId, setUserId] = useState<String>();
13+
const [image, setImage]= useState("");
14+
const [name, setName]=useState("");
15+
useEffect(()=>{
16+
const fetchDoctorInformation=async()=>{
17+
const{data:userData, error:Error}=await supabase.auth.getUser();
18+
const userId=userData?.user?.id;
19+
const {data,error}=await supabase.from('doctor').select("*").eq("id", userId).single();
20+
if(data){
21+
setImage(data.image)
22+
setName(data.name);
23+
}
24+
}
25+
fetchDoctorInformation();
26+
})
27+
return(
28+
<main className="bg-[#246BFD] flex ">
29+
<div className="mt-[30px]">
30+
<Navbar />
31+
</div>
32+
<div className="w-full bg-white rounded-l-[50px] ps-[30px] pt-[30px] overflow-x-hidden" >
33+
<ProfileSection firstName={name} lastName={doctorData?.lastName!} email={doctorData?.email!} image={image} />
34+
</div>
35+
</main>
36+
)
37+
}

Diff for: assets/images/profil.png

1.16 KB
Loading

0 commit comments

Comments
 (0)