From ef835a1300f2f717cf3b932e814ce39dcdfcc79e Mon Sep 17 00:00:00 2001 From: Shubham-Lal Date: Tue, 6 Aug 2024 12:57:18 +0530 Subject: [PATCH 1/3] client: wip user profile page --- client/src/App.tsx | 7 ++- client/src/components/card/debate-bar.css | 1 + client/src/pages/create-debate/index.tsx | 6 +- client/src/pages/notifications/style.css | 2 +- client/src/pages/profile/debates.tsx | 32 +++++++++++ client/src/pages/profile/index.tsx | 19 ++++++ client/src/pages/profile/style.css | 70 +++++++++++++++++++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 client/src/pages/profile/debates.tsx create mode 100644 client/src/pages/profile/index.tsx create mode 100644 client/src/pages/profile/style.css diff --git a/client/src/App.tsx b/client/src/App.tsx index 848fac7..5892fbf 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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() { @@ -54,16 +55,16 @@ export default function App() {
} /> - } />isScrollingUp + } /> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> - Profile Page} /> + } />
<> diff --git a/client/src/components/card/debate-bar.css b/client/src/components/card/debate-bar.css index 6371d7c..269dc9b 100644 --- a/client/src/components/card/debate-bar.css +++ b/client/src/components/card/debate-bar.css @@ -1,5 +1,6 @@ .debate-bar { position: relative; + z-index: 0; width: 100%; height: 30px; display: flex; diff --git a/client/src/pages/create-debate/index.tsx b/client/src/pages/create-debate/index.tsx index 2821882..17302e4 100644 --- a/client/src/pages/create-debate/index.tsx +++ b/client/src/pages/create-debate/index.tsx @@ -6,11 +6,11 @@ import Editor from './editor' import Preview from './preview' interface CreateProps { - isVisible: boolean + isScrollingUp: boolean isFullscreen: boolean } -const CreateDebatePage: React.FC = ({ isVisible, isFullscreen }) => { +const CreateDebatePage: React.FC = ({ isScrollingUp, isFullscreen }) => { const editorRef = useRef(null) const [debateData, setDebateData] = useState({ title: '', body: '' }) const [isPreview, setIsPreview] = useState(false) @@ -61,7 +61,7 @@ const CreateDebatePage: React.FC = ({ isVisible, isFullscreen }) => -
+
+ +
+
+ + + + + + + + +
+
+ ) +} + +export default UserDebates \ No newline at end of file diff --git a/client/src/pages/profile/index.tsx b/client/src/pages/profile/index.tsx new file mode 100644 index 0000000..d4348c3 --- /dev/null +++ b/client/src/pages/profile/index.tsx @@ -0,0 +1,19 @@ +import './style.css' +import UserDebates from './debates' + +interface ProfileProps { + isScrollingUp: boolean +} + +export default function UserProfile({ isScrollingUp }: ProfileProps) { + return ( +
+
+ +

Aniket Das

+

@aniketdas

+
+ +
+ ) +} \ No newline at end of file diff --git a/client/src/pages/profile/style.css b/client/src/pages/profile/style.css new file mode 100644 index 0000000..01a73ec --- /dev/null +++ b/client/src/pages/profile/style.css @@ -0,0 +1,70 @@ +#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: 0; + 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; +} + +#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: -60px; + transition: top 0.4s ease-out; + } +} \ No newline at end of file From 14ccb5e656e59e8d978581d6fc2c0fd34bc4cd73 Mon Sep 17 00:00:00 2001 From: Shubham-Lal Date: Wed, 7 Aug 2024 11:20:08 +0530 Subject: [PATCH 2/3] wip: profile page --- client/src/App.tsx | 2 +- .../components/modal/auth/set-password.tsx | 6 +- .../src/components/modal/auth/signup-tab.tsx | 22 ++++--- client/src/components/sidebar/profile.css | 4 ++ client/src/components/sidebar/profile.tsx | 4 +- client/src/data/regex.ts | 3 +- client/src/pages/profile/debates.tsx | 61 +++++++++++++++---- client/src/pages/profile/style.css | 5 +- .../{handleAutoLogin.ts => auto-login.ts} | 1 - 9 files changed, 76 insertions(+), 32 deletions(-) rename client/src/utils/{handleAutoLogin.ts => auto-login.ts} (96%) diff --git a/client/src/App.tsx b/client/src/App.tsx index 5892fbf..8257a78 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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' diff --git a/client/src/components/modal/auth/set-password.tsx b/client/src/components/modal/auth/set-password.tsx index f086e79..f47cf6c 100644 --- a/client/src/components/modal/auth/set-password.tsx +++ b/client/src/components/modal/auth/set-password.tsx @@ -33,7 +33,7 @@ const SetPassword = () => { })) }, []) - const handleResetSubmit = async (e: React.FormEvent) => { + const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault() setIsSubmitted(true) @@ -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 }) @@ -89,7 +89,7 @@ const SetPassword = () => { return (

Set Password

-
+

New Password

= ({ 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') @@ -114,8 +119,7 @@ const SignupTab: React.FC = ({ 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}`) } @@ -125,7 +129,7 @@ const SignupTab: React.FC = ({ registerData, setRegisterData .finally(() => setIsSubmitted(false)) } else { setTimeout(() => setIsSubmitted(false), 500) - return + return toast.warning('Please fill out all fields correctly') } } diff --git a/client/src/components/sidebar/profile.css b/client/src/components/sidebar/profile.css index 629a8f4..5244b6a 100644 --- a/client/src/components/sidebar/profile.css +++ b/client/src/components/sidebar/profile.css @@ -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; diff --git a/client/src/components/sidebar/profile.tsx b/client/src/components/sidebar/profile.tsx index 6d2e3be..73dd860 100644 --- a/client/src/components/sidebar/profile.tsx +++ b/client/src/components/sidebar/profile.tsx @@ -127,14 +127,14 @@ const Profile: React.FC = ({ isVisible }) => { onClick={handleToggleMenu} > -

Profile

+

Profile

)} diff --git a/client/src/data/regex.ts b/client/src/data/regex.ts index 012ae34..056aa88 100644 --- a/client/src/data/regex.ts +++ b/client/src/data/regex.ts @@ -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)))$/ \ No newline at end of file diff --git a/client/src/pages/profile/debates.tsx b/client/src/pages/profile/debates.tsx index b8a14be..f0ce8f9 100644 --- a/client/src/pages/profile/debates.tsx +++ b/client/src/pages/profile/debates.tsx @@ -1,32 +1,67 @@ -import { useNavStore } from "../../store/useNavStore" -import { ClosedDebateCard } from "../../components/card/closed-debate-card" -import { OpenDebateCard } from "../../components/card/open-debate-card" +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 = ({ isScrollingUp }) => { const { sidebar } = useNavStore() + const [tab, setTab] = useState(Tabs.Closed) + return (
- - + +
- - - - - - - - + {tab === Tabs.Closed ? : }
) } +const ClosedDebates = () => { + return ( + <> + + + + + + + ) +} + +const OpenDebates = () => { + return ( + <> + + + + + + + ) +} + export default UserDebates \ No newline at end of file diff --git a/client/src/pages/profile/style.css b/client/src/pages/profile/style.css index 01a73ec..05df0df 100644 --- a/client/src/pages/profile/style.css +++ b/client/src/pages/profile/style.css @@ -18,7 +18,7 @@ #profile .profile-btns { position: sticky; z-index: 1; - top: 0; + top: -1px; left: 0; padding: 20px; display: flex; @@ -33,6 +33,7 @@ #profile .profile-btns button { font-size: 18px; font-weight: 500; + text-underline-offset: 4px; } #profile .profile-btns button:hover { @@ -64,7 +65,7 @@ } #profile .profile-btns.top { - top: -60px; + top: -61px; transition: top 0.4s ease-out; } } \ No newline at end of file diff --git a/client/src/utils/handleAutoLogin.ts b/client/src/utils/auto-login.ts similarity index 96% rename from client/src/utils/handleAutoLogin.ts rename to client/src/utils/auto-login.ts index 8c63c05..6ffeed5 100644 --- a/client/src/utils/handleAutoLogin.ts +++ b/client/src/utils/auto-login.ts @@ -34,7 +34,6 @@ const handleAutoLogin = (setRoute: SetRoute, setUser: SetUser, setIsAuthenticate }) .catch(() => { setIsAuthenticated(AuthStatus.Failed) - localStorage.removeItem('token') }) } } From c7aaebeac5635bd300bb6044c1182b070b19ef8a Mon Sep 17 00:00:00 2001 From: Shubham-Lal Date: Wed, 7 Aug 2024 11:20:31 +0530 Subject: [PATCH 3/3] server: add validations --- server/controllers/auth.js | 18 +-------------- server/routes/auth.js | 46 +++++++++----------------------------- 2 files changed, 12 insertions(+), 52 deletions(-) diff --git a/server/controllers/auth.js b/server/controllers/auth.js index 783bcef..42dc150 100644 --- a/server/controllers/auth.js +++ b/server/controllers/auth.js @@ -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 @@ -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 diff --git a/server/routes/auth.js b/server/routes/auth.js index 97551d9..6f923bc 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -1,8 +1,10 @@ -const { handleGoogleAuth, register, login, autoLogin, checkUsername, recoverAccount, resetPassword } = require('../controllers/auth') +const { handleGoogleAuth, register, login, autoLogin, recoverAccount, setPassword } = require('../controllers/auth') const verifyToken = require('../middleware/verifyToken') module.exports = async function (fastify, opts) { 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)))$/ + const usernameRegex = /^[a-zA-Z0-9]+$/ + const nameRegex = /^[a-zA-Z]+$/ const registerSchema = { consumes: ['multipart/form-data'], @@ -10,9 +12,9 @@ module.exports = async function (fastify, opts) { type: 'object', properties: { email: { type: 'string', minLength: 1, maxLength: 255, pattern: emailRegex.source }, - username: { type: 'string', minLength: 1, maxLength: 15 }, - first_name: { type: 'string', minLength: 1, maxLength: 30 }, - last_name: { type: 'string', minLength: 1, maxLength: 30 }, + username: { type: 'string', minLength: 1, maxLength: 15, pattern: usernameRegex.source }, + first_name: { type: 'string', minLength: 1, maxLength: 30, pattern: nameRegex.source }, + last_name: { type: 'string', minLength: 1, maxLength: 30, pattern: nameRegex.source }, }, required: ['email', 'username', 'first_name', 'last_name'] } @@ -29,22 +31,12 @@ module.exports = async function (fastify, opts) { } } - const usernameSchema = { - body: { - type: 'object', - properties: { - username: { type: 'string', minLength: 1, maxLength: 15 }, - }, - required: ['username'] - } - } - const recoverSchema = { body: { type: 'object', properties: { email: { type: 'string', minLength: 1, maxLength: 255, pattern: emailRegex.source }, - username: { type: 'string', minLength: 1, maxLength: 15 } + username: { type: 'string', minLength: 1, maxLength: 15, pattern: usernameRegex.source } }, anyOf: [ { required: ['email'] }, @@ -53,7 +45,7 @@ module.exports = async function (fastify, opts) { } } - const resetSchema = { + const setSchema = { body: { type: 'object', properties: { @@ -115,22 +107,6 @@ module.exports = async function (fastify, opts) { } }) - fastify.post('/check-username', { - schema: usernameSchema, - attachValidation: true - }, async (request, reply) => { - if (request.validationError) { - const errors = request.validationError.validation.map(error => { - return { - field: error.params.missingProperty || error.instancePath.substring(1), - message: error.message - } - }) - return reply.code(400).send({ success: false, message: 'Validation failed', errors }) - } - return checkUsername(fastify, request, reply) - }) - fastify.post('/recover-account', { schema: recoverSchema, attachValidation: true @@ -147,8 +123,8 @@ module.exports = async function (fastify, opts) { return recoverAccount(fastify, request, reply) }) - fastify.post('/reset-password', { - schema: resetSchema, + fastify.post('/set-password', { + schema: setSchema, attachValidation: true }, async (request, reply) => { if (request.validationError) { @@ -160,6 +136,6 @@ module.exports = async function (fastify, opts) { }) return reply.code(400).send({ success: false, message: 'Validation failed', errors }) } - return resetPassword(fastify, request, reply) + return setPassword(fastify, request, reply) }) } \ No newline at end of file