From 1668785678a598ee122e4be286dbc2c6ac9b65e0 Mon Sep 17 00:00:00 2001 From: Alex van Andel Date: Wed, 23 Jun 2021 15:49:10 +0000 Subject: [PATCH] Prevent users from entering mixed case usernames Booking pages are case insensitive new, so no more case sensitive usernames. --- components/Shell.tsx | 396 ++++++++++++++++++++----------- components/ui/UsernameInput.tsx | 20 +- pages/settings/profile.tsx | 398 ++++++++++++++++++-------------- 3 files changed, 496 insertions(+), 318 deletions(-) diff --git a/components/Shell.tsx b/components/Shell.tsx index c13468bc2a..e51ff8149e 100644 --- a/components/Shell.tsx +++ b/components/Shell.tsx @@ -1,152 +1,268 @@ -import Link from 'next/link'; -import {useEffect, useState} from "react"; -import {useRouter} from "next/router"; -import {signOut, useSession} from 'next-auth/client'; -import {MenuIcon, XIcon} from '@heroicons/react/outline'; -import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../lib/telemetry"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { signOut, useSession } from "next-auth/client"; +import { MenuIcon, XIcon } from "@heroicons/react/outline"; +import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../lib/telemetry"; export default function Shell(props) { - const router = useRouter(); - const [ session, loading ] = useSession(); - const [ profileDropdownExpanded, setProfileDropdownExpanded ] = useState(false); - const [ mobileMenuExpanded, setMobileMenuExpanded ] = useState(false); - let telemetry = useTelemetry(); + const router = useRouter(); + const [session, loading] = useSession(); + const [profileDropdownExpanded, setProfileDropdownExpanded] = useState(false); + const [mobileMenuExpanded, setMobileMenuExpanded] = useState(false); + const telemetry = useTelemetry(); - useEffect(() => { - telemetry.withJitsu((jitsu) => { - return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.pathname)) - }); - }, [telemetry]) + useEffect(() => { + telemetry.withJitsu((jitsu) => { + return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.pathname)); + }); + }, [telemetry]); - const toggleProfileDropdown = () => { - setProfileDropdownExpanded(!profileDropdownExpanded); - } + const toggleProfileDropdown = () => { + setProfileDropdownExpanded(!profileDropdownExpanded); + }; - const toggleMobileMenu = () => { - setMobileMenuExpanded(!mobileMenuExpanded); - } + const toggleMobileMenu = () => { + setMobileMenuExpanded(!mobileMenuExpanded); + }; - const logoutHandler = () => { - signOut({ redirect: false }).then( () => router.push('/auth/logout') ); - } + const logoutHandler = () => { + signOut({ redirect: false }).then(() => router.push("/auth/logout")); + }; - if ( ! loading && ! session ) { - router.replace('/auth/login'); - } + if (!loading && !session) { + router.replace("/auth/login"); + } else if (loading) { + return

Loading...

; + } - return session && ( -
-
- +
+
+

{props.heading}

+
+
+
+ +
+
{props.children}
+
+
+ ); +} diff --git a/components/ui/UsernameInput.tsx b/components/ui/UsernameInput.tsx index 8b1110d0a1..bc6119379a 100644 --- a/components/ui/UsernameInput.tsx +++ b/components/ui/UsernameInput.tsx @@ -1,6 +1,6 @@ import React from "react"; -export const UsernameInput = React.forwardRef( (props, ref) => ( +const UsernameInput = React.forwardRef((props, ref) => ( // todo, check if username is already taken here?
-)); \ No newline at end of file +)); + +UsernameInput.displayName = "UsernameInput"; + +export { UsernameInput }; diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx index a00f9efaf6..c8cc0586ab 100644 --- a/pages/settings/profile.tsx +++ b/pages/settings/profile.tsx @@ -1,140 +1,173 @@ -import Head from 'next/head'; -import Link from 'next/link'; -import { useRef, useState } from 'react'; -import { useRouter } from 'next/router'; -import prisma from '../../lib/prisma'; -import Modal from '../../components/Modal'; -import Shell from '../../components/Shell'; -import SettingsShell from '../../components/Settings'; -import Avatar from '../../components/Avatar'; -import { signIn, useSession, getSession } from 'next-auth/client'; -import TimezoneSelect from 'react-timezone-select'; -import {UsernameInput} from "../../components/ui/UsernameInput"; +import { GetServerSideProps } from "next"; +import Head from "next/head"; +import { useRef, useState } from "react"; +import prisma from "../../lib/prisma"; +import Modal from "../../components/Modal"; +import Shell from "../../components/Shell"; +import SettingsShell from "../../components/Settings"; +import Avatar from "../../components/Avatar"; +import { getSession } from "next-auth/client"; +import TimezoneSelect from "react-timezone-select"; +import { UsernameInput } from "../../components/ui/UsernameInput"; import ErrorAlert from "../../components/ui/alerts/Error"; export default function Settings(props) { - const [ session, loading ] = useSession(); - const router = useRouter(); - const [successModalOpen, setSuccessModalOpen] = useState(false); - const usernameRef = useRef(); - const nameRef = useRef(); - const descriptionRef = useRef(); - const avatarRef = useRef(); + const [successModalOpen, setSuccessModalOpen] = useState(false); + const usernameRef = useRef(); + const nameRef = useRef(); + const descriptionRef = useRef(); + const avatarRef = useRef(); + const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone }); + const [selectedWeekStartDay, setSelectedWeekStartDay] = useState(props.user.weekStart || "Sunday"); - const [ selectedTimeZone, setSelectedTimeZone ] = useState({ value: props.user.timeZone }); - const [ selectedWeekStartDay, setSelectedWeekStartDay ] = useState(props.user.weekStart || 'Sunday'); + const [hasErrors, setHasErrors] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); - const [ hasErrors, setHasErrors ] = useState(false); - const [ errorMessage, setErrorMessage ] = useState(''); + const closeSuccessModal = () => { + setSuccessModalOpen(false); + }; - if (loading) { - return

Loading...

; + const handleError = async (resp) => { + if (!resp.ok) { + const error = await resp.json(); + throw new Error(error.message); } + }; - const closeSuccessModal = () => { setSuccessModalOpen(false); } + async function updateProfileHandler(event) { + event.preventDefault(); - const handleError = async (resp) => { - if (!resp.ok) { - const error = await resp.json(); - throw new Error(error.message); - } - } + const enteredUsername = usernameRef.current.value.toLowerCase(); + const enteredName = nameRef.current.value; + const enteredDescription = descriptionRef.current.value; + const enteredAvatar = avatarRef.current.value; + const enteredTimeZone = selectedTimeZone.value; + const enteredWeekStartDay = selectedWeekStartDay; - async function updateProfileHandler(event) { - event.preventDefault(); + // TODO: Add validation - const enteredUsername = usernameRef.current.value; - const enteredName = nameRef.current.value; - const enteredDescription = descriptionRef.current.value; - const enteredAvatar = avatarRef.current.value; - const enteredTimeZone = selectedTimeZone.value; - const enteredWeekStartDay = selectedWeekStartDay; + await fetch("/api/user/profile", { + method: "PATCH", + body: JSON.stringify({ + username: enteredUsername, + name: enteredName, + description: enteredDescription, + avatar: enteredAvatar, + timeZone: enteredTimeZone, + weekStart: enteredWeekStartDay, + }), + headers: { + "Content-Type": "application/json", + }, + }) + .then(handleError) + .then(() => { + setSuccessModalOpen(true); + setHasErrors(false); // dismiss any open errors + }) + .catch((err) => { + setHasErrors(true); + setErrorMessage(err.message); + }); + } - // TODO: Add validation + return ( + + + Profile | Calendso + + + +
+ {hasErrors && } +
+
+

Profile

+

Review and change your public page details.

+
- const response = await fetch('/api/user/profile', { - method: 'PATCH', - body: JSON.stringify({username: enteredUsername, name: enteredName, description: enteredDescription, avatar: enteredAvatar, timeZone: enteredTimeZone, weekStart: enteredWeekStartDay}), - headers: { - 'Content-Type': 'application/json' - } - }).then(handleError).then( () => { - setSuccessModalOpen(true); - setHasErrors(false); // dismiss any open errors - }).catch( (err) => { - setHasErrors(true); - setErrorMessage(err.message); - }); - } +
+
+
+
+ +
+
+ + +
+
- return( - - - Profile | Calendso - - - - - {hasErrors && } -
-
-

Profile

-

- Review and change your public page details. -

-
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
-
-
-
-
- -
-
- - -
-
- -
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
- - {/*
+
+ +
+
+ + {/*
*/} -
-
+
+
-
-
} - /> - {/*
-
- - -
-
-
-
-
- -
-
- - -
-
- ); +
+
+ + +
+
+
+
+
+ +
+ + + +
+
+ ); } -export async function getServerSideProps(context) { - const session = await getSession(context); - if (!session) { - return { redirect: { permanent: false, destination: '/auth/login' } }; - } +export const getServerSideProps: GetServerSideProps = async (context) => { + const session = await getSession(context); + if (!session) { + return { redirect: { permanent: false, destination: "/auth/login" } }; + } - const user = await prisma.user.findFirst({ - where: { - email: session.user.email, - }, - select: { - id: true, - username: true, - name: true, - email: true, - bio: true, - avatar: true, - timeZone: true, - weekStart: true, - } - }); + const user = await prisma.user.findFirst({ + where: { + email: session.user.email, + }, + select: { + id: true, + username: true, + name: true, + email: true, + bio: true, + avatar: true, + timeZone: true, + weekStart: true, + }, + }); - return { - props: {user}, // will be passed to the page component as props - } -} \ No newline at end of file + return { + props: { user }, // will be passed to the page component as props + }; +};