Skip to content

Commit

Permalink
add user profile page
Browse files Browse the repository at this point in the history
  • Loading branch information
Shubham-Lal authored Aug 7, 2024
2 parents 0f93753 + c7aaebe commit 367556e
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 76 deletions.
9 changes: 5 additions & 4 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Toaster } from 'sonner'
import { ProtectedRoute } from './ProtectedRoute'
import { Theme, useNavStore } from './store/useNavStore'
import { AuthTab, useAuthStore } from './store/useAuthStore'
import handleAutoLogin from './utils/handleAutoLogin'
import handleAutoLogin from './utils/auto-login'
import LeftSidebar from './components/sidebar/left-sidebar'
import RightSidebar from './components/sidebar/right-sidebar'
import AuthModal from './components/modal/auth'
Expand All @@ -16,6 +16,7 @@ import CreateDebatePage from './pages/create-debate'
import HotTopicsPage from './pages/hot-topics'
import OpenTopicsPage from './pages/open-topics'
import NotificationPage from './pages/notifications'
import UserProfile from './pages/profile'
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'

export default function App() {
Expand Down Expand Up @@ -54,16 +55,16 @@ export default function App() {
<main id='main' ref={mainRef} className={`${expand ? 'expand' : ''} ${sidebar ? 'w-full' : ''}`}>
<Routes>
<Route path='/' element={<HomePage />} />
<Route path='/auth' element={<AuthPage />} />isScrollingUp
<Route path='/auth' element={<AuthPage />} />
<Route path='/login' element={<Navigate to='/auth?type=login' />} />
<Route path='/signup' element={<Navigate to='/auth?type=signup' />} />
<Route path='/forgot' element={<Navigate to='/auth?type=forgot' />} />
<Route path='/search' element={<SearchPage />} />
<Route path='/create' element={<ProtectedRoute><CreateDebatePage isVisible={isScrollingUp} isFullscreen={!sidebar} /></ProtectedRoute>} />
<Route path='/create' element={<ProtectedRoute><CreateDebatePage isScrollingUp={isScrollingUp} isFullscreen={!sidebar} /></ProtectedRoute>} />
<Route path='/hot-topics' element={<HotTopicsPage />} />
<Route path='/open-topics' element={<OpenTopicsPage />} />
<Route path='/notifications' element={<NotificationPage />} />
<Route path=':username' element={<>Profile Page</>} />
<Route path=':username' element={<UserProfile isScrollingUp={isScrollingUp} />} />
</Routes>
</main>
<>
Expand Down
1 change: 1 addition & 0 deletions client/src/components/card/debate-bar.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.debate-bar {
position: relative;
z-index: 0;
width: 100%;
height: 30px;
display: flex;
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/modal/auth/set-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const SetPassword = () => {
}))
}, [])

const handleResetSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setIsSubmitted(true)

Expand Down Expand Up @@ -61,7 +61,7 @@ const SetPassword = () => {
return toast.warning('Password should be atleast 6 digits')
}

await fetch(`${import.meta.env.VITE_SERVER_URL}/api/auth/reset-password`, {
await fetch(`${import.meta.env.VITE_SERVER_URL}/api/auth/set-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: new URLSearchParams(location.search).get('token'), password: trimmedNew })
Expand Down Expand Up @@ -89,7 +89,7 @@ const SetPassword = () => {
return (
<div id='reset'>
<h3>Set Password</h3>
<form id='forgot-form' className='form__container' onSubmit={handleResetSubmit}>
<form id='forgot-form' className='form__container' onSubmit={handleFormSubmit}>
<div className='input__container'>
<p>New Password</p>
<input
Expand Down
22 changes: 13 additions & 9 deletions client/src/components/modal/auth/signup-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RegisterDataProps } from '../../../types'
import { toast } from 'sonner'
import { AuthTab, useAuthStore, useTempStore } from '../../../store/useAuthStore'
import { LoadingSVG } from '../../loading/svg'
import { specialCharRegex, usernameRegex, emailRegex } from '../../../data/regex'
import { specialCharRegex, usernameRegex, emailRegex, nameRegex } from '../../../data/regex'
import { MdModeEdit } from 'react-icons/md'
import { GrCloudUpload } from 'react-icons/gr'
import { IoPersonCircleOutline } from 'react-icons/io5'
Expand Down Expand Up @@ -82,14 +82,19 @@ const SignupTab: React.FC<RegisterDataProps> = ({ registerData, setRegisterData
email: trimmedEmail
}))

const isUsernameValid = trimmedUsername.length > 0 && usernameRegex.test(trimmedUsername)
const isFirstNameValid = trimmedFirstName.length > 0 && nameRegex.test(trimmedFirstName)
const isLastNameValid = trimmedLastName.length > 0 && nameRegex.test(trimmedLastName)
const isEmailValid = trimmedEmail.length > 0 && emailRegex.test(trimmedEmail)

setValidationState({
isUsernameValid: !!trimmedUsername,
isFirstNameValid: !!trimmedFirstName,
isLastNameValid: !!trimmedLastName,
isEmailValid: !!trimmedEmail
isUsernameValid,
isFirstNameValid,
isLastNameValid,
isEmailValid
})

if (trimmedUsername && trimmedFirstName && trimmedLastName && trimmedEmail && term) {
if (isUsernameValid && isFirstNameValid && isLastNameValid && isEmailValid && term) {
if (!emailRegex.test(trimmedEmail)) {
setTimeout(() => setIsSubmitted(false), 500)
return toast.warning('Invalid email address')
Expand All @@ -114,8 +119,7 @@ const SignupTab: React.FC<RegisterDataProps> = ({ registerData, setRegisterData
localStorage.removeItem('username')
toast.success(response.message)
navigate('/')
}
else {
} else {
if (response.message === 'Validation failed') {
return toast.error(`${response.errors[0].field.charAt(0).toUpperCase() + response.errors[0].field.slice(1)} ${response.errors[0].message}`)
}
Expand All @@ -125,7 +129,7 @@ const SignupTab: React.FC<RegisterDataProps> = ({ registerData, setRegisterData
.finally(() => setIsSubmitted(false))
} else {
setTimeout(() => setIsSubmitted(false), 500)
return
return toast.warning('Please fill out all fields correctly')
}
}

Expand Down
4 changes: 4 additions & 0 deletions client/src/components/sidebar/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
line-height: 25px;
}

.modal-profile-btn:hover {
text-decoration: underline;
}

@media screen and (max-width: 767px) {
.profile__wrapper .auth-btn {
width: 40px;
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/sidebar/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ const Profile: React.FC<ProfileProps> = ({ isVisible }) => {
onClick={handleToggleMenu}
>
<IoMdPerson size={18} />
<p className='underline'>Profile</p>
<p>Profile</p>
</Link>
<button
className='modal-profile-btn'
onClick={handleLogout}
>
<PiSignOutBold size={18} />
<p className='underline'>Logout</p>
<p>Logout</p>
</button>
</div>
)}
Expand Down
3 changes: 2 additions & 1 deletion client/src/data/regex.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const usernameRegex = /^[a-zA-Z0-9_-]+$/
export const specialCharRegex = /[@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/ // eslint-disable-line
export const usernameRegex = /^[a-zA-Z0-9]+$/
export const nameRegex = /^[a-zA-Z]+$/
export const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+(com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|in|space)))$/
6 changes: 3 additions & 3 deletions client/src/pages/create-debate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import Editor from './editor'
import Preview from './preview'

interface CreateProps {
isVisible: boolean
isScrollingUp: boolean
isFullscreen: boolean
}

const CreateDebatePage: React.FC<CreateProps> = ({ isVisible, isFullscreen }) => {
const CreateDebatePage: React.FC<CreateProps> = ({ isScrollingUp, isFullscreen }) => {
const editorRef = useRef<RichTextEditorComponent>(null)
const [debateData, setDebateData] = useState({ title: '', body: '' })
const [isPreview, setIsPreview] = useState<boolean>(false)
Expand Down Expand Up @@ -61,7 +61,7 @@ const CreateDebatePage: React.FC<CreateProps> = ({ isVisible, isFullscreen }) =>

<Preview isPreview={isPreview} editorRef={editorRef} debateData={debateData} />

<div className={`debate-btns ${isVisible ? 'reveal' : 'hide'} ${isFullscreen ? '' : 'w-full'}`}>
<div className={`debate-btns ${isScrollingUp ? 'reveal' : 'hide'} ${isFullscreen ? '' : 'w-full'}`}>
<button
type='button'
onClick={handlePreviewToggle}
Expand Down
2 changes: 1 addition & 1 deletion client/src/pages/notifications/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

@media screen and (max-width: 480px) {
#notification {
padding: 10px;
padding: 20px 10px;
}
}
67 changes: 67 additions & 0 deletions client/src/pages/profile/debates.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { useState } from 'react'
import { useNavStore } from '../../store/useNavStore'
import { ClosedDebateCard } from '../../components/card/closed-debate-card'
import { OpenDebateCard } from '../../components/card/open-debate-card'

interface DebatesProps {
isScrollingUp: boolean
}

enum Tabs {
Closed = 'closed-debates',
Open = 'open-debates'
}

const UserDebates: React.FC<DebatesProps> = ({ isScrollingUp }) => {
const { sidebar } = useNavStore()

const [tab, setTab] = useState<Tabs>(Tabs.Closed)

return (
<div>
<div className={`profile-btns ${isScrollingUp ? '' : 'top'}`}>
<button
style={{ textDecoration: tab === Tabs.Closed ? 'underline' : '' }}
onClick={() => setTab(Tabs.Closed)}
>
Closed Debates
</button>
<button
style={{ textDecoration: tab === Tabs.Open ? 'underline' : '' }}
onClick={() => setTab(Tabs.Open)}
>
Open Debates
</button>
</div>
<div className={`debates ${sidebar ? 'column-debates' : ''}`}>
{tab === Tabs.Closed ? <ClosedDebates /> : <OpenDebates />}
</div>
</div>
)
}

const ClosedDebates = () => {
return (
<>
<ClosedDebateCard />
<ClosedDebateCard />
<ClosedDebateCard />
<ClosedDebateCard />
<ClosedDebateCard />
</>
)
}

const OpenDebates = () => {
return (
<>
<OpenDebateCard />
<OpenDebateCard />
<OpenDebateCard />
<OpenDebateCard />
<OpenDebateCard />
</>
)
}

export default UserDebates
19 changes: 19 additions & 0 deletions client/src/pages/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import './style.css'
import UserDebates from './debates'

interface ProfileProps {
isScrollingUp: boolean
}

export default function UserProfile({ isScrollingUp }: ProfileProps) {
return (
<div id='profile'>
<div className='user'>
<img src='/user1.webp' alt='' />
<h1>Aniket Das</h1>
<h3>@aniketdas</h3>
</div>
<UserDebates isScrollingUp={isScrollingUp} />
</div>
)
}
71 changes: 71 additions & 0 deletions client/src/pages/profile/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#profile .user {
padding: 20px;
}

#profile .user img {
width: 100px;
height: 100px;
object-fit: cover;
background-color: var(--body_background);
border-radius: 10px;
border: 2px solid var(--explore_input_bg);
}

#profile .user h3 {
color: var(--card_color_secondary);
}

#profile .profile-btns {
position: sticky;
z-index: 1;
top: -1px;
left: 0;
padding: 20px;
display: flex;
align-items: center;
gap: 20px;
background-color: var(--body_background);
border-top: 1px solid var(--explore_input_bg);
border-bottom: 1px solid var(--explore_input_bg);
transition: top 0.25s ease-out;
}

#profile .profile-btns button {
font-size: 18px;
font-weight: 500;
text-underline-offset: 4px;
}

#profile .profile-btns button:hover {
text-decoration: underline;
}

@media screen and (max-width: 767px) {
#profile .user img {
width: 75px;
height: 75px;
}
}

@media screen and (max-width: 480px) {
#profile .user {
padding: 20px 10px;
}

#profile .user h1 {
font-size: 24px;
}

#profile .user h3 {
font-size: 16px;
}

#profile .profile-btns {
padding: 10px;
}

#profile .profile-btns.top {
top: -61px;
transition: top 0.4s ease-out;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const handleAutoLogin = (setRoute: SetRoute, setUser: SetUser, setIsAuthenticate
})
.catch(() => {
setIsAuthenticated(AuthStatus.Failed)
localStorage.removeItem('token')
})
}
}
Expand Down
18 changes: 1 addition & 17 deletions server/controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,22 +176,6 @@ exports.autoLogin = async function (fastify, request, reply) {
}
}

exports.checkUsername = async function (fastify, request, reply) {
try {
const { username } = request.body

const [user] = await fastify.mysql.query('SELECT * FROM users WHERE username=?', [username])
if (user.length) throw new ErrorHandler(400, false, 'Username already taken')

return reply.code(200).send({
success: true,
message: 'Username available'
})
} catch (err) {
return catchError(reply, err)
}
}

exports.recoverAccount = async function (fastify, request, reply) {
try {
const { email, username } = request.body
Expand Down Expand Up @@ -225,7 +209,7 @@ exports.recoverAccount = async function (fastify, request, reply) {
}
}

exports.resetPassword = async (fastify, request, reply) => {
exports.setPassword = async (fastify, request, reply) => {
try {
const { token, password } = request.body

Expand Down
Loading

0 comments on commit 367556e

Please sign in to comment.