Skip to content

Commit c97479d

Browse files
Login, register and logout logic completed (#4)
* Login, register and logout logic completed * Update react and react-dom to 18.2.0 for compatibility with React Testing Library --------- Co-authored-by: Amanuel Abera <[email protected]>
1 parent b0500d6 commit c97479d

File tree

26 files changed

+1024
-78
lines changed

26 files changed

+1024
-78
lines changed

app/(auth)/login/page.tsx

+82-29
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,112 @@
11
'use client'
2-
2+
import { useRouter } from 'next/navigation';
3+
import { login } from "@/utils/actions";
34
import Image from "next/image"
45
import Link from "next/link";
5-
import { useState } from "react";
6+
import { useEffect, useState } from "react";
7+
import { useActionState } from "react";
68
import { FaEye } from "react-icons/fa";
79
import { IoCheckbox } from "react-icons/io5";
810
import { MdCheckBoxOutlineBlank } from "react-icons/md";
11+
import { startTransition } from "react";
12+
import { useGlobalContext } from '@/context/context';
13+
14+
const initialState: {
15+
message:string | null, errors?: Record<string, string[] | undefined>,
16+
isLoggedIn?:boolean,
17+
accessToken?: string | null,
18+
userId?: string | null
19+
} = {
20+
message:null,
21+
isLoggedIn:false,
22+
errors:{},
23+
accessToken: null,
24+
userId: null
25+
}
926

1027
const Login = () => {
1128
const [isChecked, setIsChecked] = useState<boolean>(false);
1229
const [togglePassword, setTogglePassword] = useState<boolean>(false);
30+
const [state, formAction] = useActionState(login, initialState);
31+
const [loading, setLoading] = useState<boolean>(false);
32+
const {setUserId} = useGlobalContext();
33+
const router = useRouter();
34+
35+
const handleCheckboxToggle = () => {
36+
setIsChecked((prev) => !prev);
37+
};
38+
39+
const handleSubmit = async (e:React.FormEvent<HTMLElement>) => {
40+
e.preventDefault();
41+
setLoading(true);
42+
const formData = new FormData(e.currentTarget as HTMLFormElement)
43+
startTransition(() => {
44+
formAction(formData);
45+
setLoading(false);
46+
});
47+
48+
}
49+
50+
useEffect(()=>{
51+
if(state?.isLoggedIn){
52+
const userId = state?.userId as string
53+
setUserId(userId)
54+
router.push("/")
55+
}
56+
},[state])
1357

14-
const handleCheckboxToggle = () => {
15-
setIsChecked((prev) => !prev);
16-
};
17-
18-
return (
58+
return (
1959
<div className="grid grid-cols-5 gap-8 mx-5 md:gap-12 max-w-screen" style={{ height: 'calc(100vh - 4rem)' }}>
20-
<form action="" method='POST' className="col-span-5 max-w-lg sm:max-w-4xl mt-8 sm:mt-16 sm:col-span-3 md:col-span-2">
60+
<form onSubmit={handleSubmit} method='POST' className="col-span-5 max-w-lg sm:max-w-4xl mt-8 sm:mt-16 sm:col-span-3 md:col-span-2">
2161
<h1 className="heading text-2xl font-bold">Welcome!</h1>
2262
<p className="para mb-5 mt-2">Log In your account.</p>
63+
{(state?.errors?.root) && <>
64+
<div className="flex gap-2">
65+
<p className="error-message font-bold mb-4 text-sm">
66+
{state?.errors?.root}
67+
</p>
68+
</div>
69+
</>}
2370
<div className="flex flex-col mb-5">
2471
<label className="label" htmlFor="email">Email <span className="asterik">*</span></label>
25-
<input type="email" className="input value w-full mt-2" id="email" placeholder="Your Email" required/>
72+
<input type="email" name="email" className="input value w-full mt-2" id="email" placeholder="Your Email" required/>
73+
{state.errors && state.errors.email && (
74+
<p className="error-message">{state.errors.email}</p>
75+
)}
2676
</div>
2777
<div className="flex flex-col mb-5 relative">
2878
<label className="label" htmlFor="password">Password <span className="asterik">*</span></label>
2979
<div className="flex mt-2 relative">
30-
<input type={togglePassword ?"text" :"password"} className="input value w-full pr-12" id="password" placeholder="Password" required/>
80+
<input type={togglePassword ?"text" :"password"} name="password" className="input value w-full pr-12" id="password" placeholder="Password" required/>
3181
<div className="flex justify-center items-center py-1 px-4 rounded-lg box-border cursor-pointer absolute top-0 right-0 bottom-0" onClick={()=>setTogglePassword(!togglePassword)}>
3282
<FaEye className="value"/>
3383
</div>
3484
</div>
85+
{state.errors && state.errors.password && (
86+
<p className="error-message">{state.errors.password}</p>
87+
)}
3588
</div>
3689
<div className="flex justify-between items-center">
37-
<label className="label flex justify-between items-center gap-2 w-full">
38-
<div className="label flex items-center gap-2">
39-
{isChecked ? (
40-
<IoCheckbox
41-
className="primary border-0 text-xl"
42-
onClick={handleCheckboxToggle}
43-
/>
44-
) : (
45-
<MdCheckBoxOutlineBlank
46-
className="primary border-0 text-xl"
47-
onClick={handleCheckboxToggle}
48-
/>
49-
)}
50-
<input type="checkbox" hidden />
51-
Remember me
52-
</div>
53-
<Link href={""} className="label hover:underline">Forget Password?</Link>
54-
</label>
90+
<label className="label flex justify-between items-center gap-2 w-full">
91+
<div className="label flex items-center gap-2">
92+
{isChecked ? (
93+
<IoCheckbox
94+
className="primary border-0 text-xl"
95+
onClick={handleCheckboxToggle}
96+
/>
97+
) : (
98+
<MdCheckBoxOutlineBlank
99+
className="primary border-0 text-xl"
100+
onClick={handleCheckboxToggle}
101+
/>
102+
)}
103+
<input type="checkbox" hidden data-testid="checkbox"/>
104+
Remember me
105+
</div>
106+
<Link href={""} className="label hover:underline">Forget Password?</Link>
107+
</label>
55108
</div>
56-
<button className="primaryBtn w-full mt-6">Login</button>
109+
<button className="primaryBtn w-full mt-6" disabled={loading} data-testid="submit">{loading ? "Processing...": "Login"}</button>
57110
<p className="para text-sm mt-4 text-center">Don&#39;t have any account? <Link href={'/register'} className="heading font-bold">SignUp</Link></p>
58111
</form>
59112
<div className="relative hidden sm:block col-span-1 sm:col-span-2 md:col-span-3 h-full">

app/(auth)/register/page.tsx

+65-16
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,105 @@
33
import Image from "next/image"
44
import Link from "next/link";
55
import { useState } from "react";
6+
import { useFormState } from "react-dom";
67
import { FaEye } from "react-icons/fa";
7-
import { IoCheckbox } from "react-icons/io5";
8-
import { MdCheckBoxOutlineBlank } from "react-icons/md";
8+
import { startTransition } from "react";
9+
10+
11+
12+
// actions
13+
import { createUser } from "@/utils/actions";
14+
15+
const initialState: { message: string | null; errors?: Record<string, string[] | undefined> } = {
16+
message: null,
17+
errors: {},
18+
};
919

1020
const Register = () => {
11-
const [isChecked, setIsChecked] = useState<boolean>(false);
1221
const [togglePassword, setTogglePassword] = useState<boolean>(false);
1322
const [toggleConfirmPassword, setToggleConfirmPasword] = useState<boolean>(false);
23+
const [loading, setLoading] = useState<boolean>(false);
24+
const [state,formAction] = useFormState(createUser,initialState)
1425

26+
const handleSubmit = async (e:React.FormEvent<HTMLElement>) => {
27+
e.preventDefault();
28+
setLoading(true);
29+
const formData = new FormData(e.currentTarget as HTMLFormElement)
30+
startTransition(() => {
31+
formAction(formData);
32+
setLoading(false);
33+
});
1534

16-
const handleCheckboxToggle = () => {
17-
setIsChecked((prev) => !prev);
18-
};
35+
}
1936

2037
return (
2138
<div className="grid grid-cols-5 gap-8 mx-5 md:gap-12 max-w-screen" style={{ height: 'calc(100vh - 4rem)' }}>
22-
<form action="" method='POST' className="col-span-5 max-w-lg sm:max-w-4xl mt-8 sm:mt-16 sm:col-span-3 md:col-span-2">
39+
<form onSubmit={handleSubmit} action={formAction} method='POST' className="col-span-5 max-w-lg sm:max-w-4xl mt-8 sm:mt-16 sm:col-span-3 md:col-span-2">
2340
<h1 className="heading text-2xl font-bold">Register!</h1>
2441
<p className="para mb-5 mt-2">Create a new account.</p>
42+
43+
{(state?.message) && <>
44+
<div className="flex gap-2">
45+
<p className="success-message text-green-500 mb-4 font-bold">
46+
{state?.message}
47+
</p>
48+
<Link href={"/login"} className="heading underline cursor-pointer font-bold text-xs">Login here</Link>
49+
</div>
50+
</>}
51+
{(state?.errors?.root) && <>
52+
<div className="flex gap-2">
53+
<p className="error-message font-bold mb-4 text-sm">
54+
{state?.errors?.root}
55+
</p>
56+
</div>
57+
</>}
2558
<div className="flex flex-col mb-5">
2659
<label className="label" htmlFor="firstName">First Name <span className="asterik">*</span></label>
27-
<input type="text" className="input value w-full mt-2" id="firstName" placeholder="First Name" required/>
60+
<input type="text" name="firstName" className="input value w-full mt-2" id="firstName" placeholder="First Name" required/>
61+
{state.errors && state.errors.firstName && (
62+
<p className="error-message">{state.errors.firstName}</p>
63+
)}
2864
</div>
2965
<div className="flex flex-col mb-5">
3066
<label className="label" htmlFor="lastName">Last Name <span className="asterik">*</span></label>
31-
<input type="text" className="input value w-full mt-2" id="lastName" placeholder="Last Name" required/>
67+
<input type="text" name="lastName" className="input value w-full mt-2" id="lastName" placeholder="Last Name" required/>
68+
{state.errors && state.errors.lastName && (
69+
<p className="error-message">{state.errors.lastName}</p>
70+
)}
3271
</div>
3372
<div className="flex flex-col mb-5">
3473
<label className="label" htmlFor="email">Email <span className="asterik">*</span></label>
35-
<input type="email" className="input value w-full mt-2" id="email" placeholder="Your Email" required/>
74+
<input type="email" name="email" className="input value w-full mt-2" id="email" placeholder="Your Email" required/>
75+
{state.errors && state.errors.email && (
76+
<p className="error-message">{state.errors.email}</p>
77+
)}
3678
</div>
37-
<div className="flex flex-col mb-5 relative">
79+
<div className="flex flex-col mb-5 relative z-0">
3880
<label className="label" htmlFor="password">Password <span className="asterik">*</span></label>
3981
<div className="flex mt-2 relative">
40-
<input type={togglePassword ?"text" :"password"} className="input value w-full pr-12" id="password" placeholder="Password" required/>
82+
<input type={togglePassword ?"text" :"password"} name="password" className="input value w-full pr-12" id="password" placeholder="Password" required/>
4183
<div className="flex justify-center items-center py-1 px-4 rounded-lg box-border cursor-pointer absolute top-0 right-0 bottom-0" onClick={()=>setTogglePassword(!togglePassword)}>
4284
<FaEye className="value"/>
4385
</div>
4486
</div>
87+
{state.errors && state.errors.password && (
88+
<p className="error-message">{state.errors.password}</p>
89+
)}
4590

4691
</div>
4792
<div className="flex flex-col mb-5 relative w-full">
4893
<label className="label" htmlFor="confirmPassword">Confirm Password <span className="asterik">*</span></label>
4994
<div className="flex mt-2 relative">
50-
<input type={toggleConfirmPassword ?"text" :"password"} className="input value w-full pr-12" id="confirmPassword" placeholder="Confirm Password" required/>
95+
<input type={toggleConfirmPassword ?"text" :"password"} name="confirmPassword" className="input value w-full pr-12" id="confirmPassword" placeholder="Confirm Password" required />
5196
<div className="flex justify-center items-center py-1 px-4 rounded-lg box-border cursor-pointer absolute top-0 right-0 bottom-0" onClick={()=>setToggleConfirmPasword(!toggleConfirmPassword)}>
5297
<FaEye className="value"/>
5398
</div>
5499
</div>
100+
{state.errors && state.errors.confirmPassword && (
101+
<p className="error-message">{state.errors.confirmPassword}</p>
102+
)}
55103
</div>
56-
<div>
104+
{/* <div>
57105
<label className="label flex items-center gap-2">
58106
{isChecked ? (
59107
<IoCheckbox
@@ -69,8 +117,9 @@ const Register = () => {
69117
<input type="checkbox" hidden />
70118
Remember me
71119
</label>
72-
</div>
73-
<button className="primaryBtn w-full mt-6">Register</button>
120+
</div> */}
121+
<button className="primaryBtn w-full mt-6" disabled={loading}>{loading ? "Processing...": "Register"}</button>
122+
74123
<p className="para text-sm mt-4 text-center">Do you have an account? <Link href={'/login'} className="heading font-bold">SignIn</Link></p>
75124
</form>
76125
<div className="relative hidden sm:block col-span-1 sm:col-span-2 md:col-span-3 h-full">

app/(profile)/create-post/page.tsx

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react'
2+
import { FaImage } from "react-icons/fa";
3+
4+
const CreateBlog = () => {
5+
return (
6+
<div className='background p-6 rounded-lg max-w-4xl'>
7+
<h1 className='text-xl font-bold mb-8'>Create a new blog</h1>
8+
<form>
9+
<p className='label mb-3'>Upload blog cover&nbsp;<span className='asterik'>*</span></p>
10+
<label htmlFor="blogCover" className='border-2 border-dashed rounded-lg h-40 flex flex-col gap-2 justify-center items-center cursor-pointer mb-5 secondaryBg'>
11+
<FaImage className='label text-3xl'/>
12+
<p className='text-xs'>Upload Blog Cover Image</p>
13+
<p className='text-xs'>click to browse</p>
14+
<input type='file' name='blogCover' id='blogCover' className='hidden'/>
15+
</label>
16+
<div className="flex flex-col mb-5">
17+
<label className="label" htmlFor="blogName">Blog Name <span className="asterik">*</span></label>
18+
<input
19+
type="text"
20+
name="blogName"
21+
className="input value w-full mt-2"
22+
id="blogName"
23+
required
24+
placeholder='Eg. Next.js 15 is know released.'
25+
/>
26+
{/* {state.errors && state.errors.email && (
27+
<p className="error-message">{state.errors.email}</p>
28+
)} */}
29+
</div>
30+
<div className="flex flex-col mb-5">
31+
<label className="label" htmlFor="hook">Hook <span className="asterik">*</span></label>
32+
<input
33+
type="text"
34+
name="hook"
35+
className="input value w-full mt-2"
36+
id="hook"
37+
placeholder='Eg. Did you know why next.js is so important? I tell you why'
38+
required
39+
/>
40+
{/* {state.errors && state.errors.email && (
41+
<p className="error-message">{state.errors.email}</p>
42+
)} */}
43+
</div>
44+
<div className="flex flex-col mb-5">
45+
<label className="label" htmlFor="desc">
46+
Description <span className="asterik">*</span>
47+
</label>
48+
<textarea
49+
name="desc"
50+
id="desc"
51+
className="input value w-full mt-2"
52+
rows={10}
53+
required
54+
placeholder='Eg. <h1>What is Next.js.</h1>'
55+
/>
56+
{/* {state.errors?.desc && (
57+
<p className="error-message">{state.errors.desc}</p>
58+
)} */}
59+
</div>
60+
<p className='label mb-3'>Tags&nbsp;<span className='asterik'>*</span></p>
61+
<div className='myBorder rounded-lg h-24 p-3 gap-2 items-center cursor-pointer mb-5 flex flex-wrap justify-start'>
62+
<span className='tag cursor-pointer hover:tagSelected'>Design</span>
63+
<span className='tag cursor-pointer hover:tagSelected'>Research</span>
64+
<span className='tag cursor-pointer hover:tagSelected'>Technology</span>
65+
<span className='tag cursor-pointer hover:tagSelected'>Polotics</span>
66+
</div>
67+
<div className='flex justify-between mt-10'>
68+
<button className='transparentBtn'>Cancel</button>
69+
<div className='flex gap-7'>
70+
<button className='secondaryBtn'>Save as Draft</button>
71+
<button className='primaryBtn'>Publish</button>
72+
</div>
73+
</div>
74+
75+
</form>
76+
</div>
77+
)
78+
}
79+
80+
export default CreateBlog

0 commit comments

Comments
 (0)