Merge pull request #303 from emrysal/bugfix/prevent-uppercase-usernames-going-forward
Prevent users from entering mixed case usernames
This commit is contained in:
		
						commit
						b12198e3a6
					
				
					 3 changed files with 496 additions and 318 deletions
				
			
		| 
						 | 
				
			
			@ -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 <p className="text-gray-400">Loading...</p>;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    return session && (
 | 
			
		||||
        <div>
 | 
			
		||||
            <div className="bg-gradient-to-b from-blue-600 via-blue-600 to-blue-300 pb-32">
 | 
			
		||||
                <nav className="bg-blue-600">
 | 
			
		||||
                    <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
 | 
			
		||||
                        <div className="border-b border-blue-500">
 | 
			
		||||
                            <div className="flex items-center justify-between h-16 px-4 sm:px-0">
 | 
			
		||||
                                <div className="flex items-center">
 | 
			
		||||
                                    <div className="flex-shrink-0">
 | 
			
		||||
                                        <img className="h-6" src="/calendso-white.svg" alt="Calendso" />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div className="hidden md:block">
 | 
			
		||||
                                        <div className="ml-10 flex items-baseline space-x-4">
 | 
			
		||||
                                            <Link href="/">
 | 
			
		||||
                                                <a className={router.pathname == "/" ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Dashboard</a>
 | 
			
		||||
                                            </Link>
 | 
			
		||||
                                            {/* <Link href="/">
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div className="bg-gradient-to-b from-blue-600 via-blue-600 to-blue-300 pb-32">
 | 
			
		||||
        <nav className="bg-blue-600">
 | 
			
		||||
          <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
 | 
			
		||||
            <div className="border-b border-blue-500">
 | 
			
		||||
              <div className="flex items-center justify-between h-16 px-4 sm:px-0">
 | 
			
		||||
                <div className="flex items-center">
 | 
			
		||||
                  <div className="flex-shrink-0">
 | 
			
		||||
                    <img className="h-6" src="/calendso-white.svg" alt="Calendso" />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div className="hidden md:block">
 | 
			
		||||
                    <div className="ml-10 flex items-baseline space-x-4">
 | 
			
		||||
                      <Link href="/">
 | 
			
		||||
                        <a
 | 
			
		||||
                          className={
 | 
			
		||||
                            router.pathname == "/"
 | 
			
		||||
                              ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                              : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                          }>
 | 
			
		||||
                          Dashboard
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </Link>
 | 
			
		||||
                      {/* <Link href="/">
 | 
			
		||||
                                                <a className={router.pathname.startsWith("/bookings") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Bookings</a>
 | 
			
		||||
                                            </Link> */}
 | 
			
		||||
                                            <Link href="/availability">
 | 
			
		||||
                                                <a className={router.pathname.startsWith("/availability") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Availability</a>
 | 
			
		||||
                                            </Link>
 | 
			
		||||
                                            <Link href="/integrations">
 | 
			
		||||
                                                <a className={router.pathname.startsWith("/integrations") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Integrations</a>
 | 
			
		||||
                                            </Link>
 | 
			
		||||
                                            <Link href="/settings/profile">
 | 
			
		||||
                                                <a className={router.pathname.startsWith("/settings") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Settings</a>
 | 
			
		||||
                                            </Link>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className="hidden md:block">
 | 
			
		||||
                                    <div className="ml-4 flex items-center md:ml-6">
 | 
			
		||||
                                        <div className="ml-3 relative">
 | 
			
		||||
                                            <div>
 | 
			
		||||
                                                <button onClick={toggleProfileDropdown} type="button" className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu" aria-expanded="false" aria-haspopup="true">
 | 
			
		||||
                                                    <span className="sr-only">Open user menu</span>
 | 
			
		||||
                                                    <img className="h-8 w-8 rounded-full" src={session.user.image ? session.user.image : "https://eu.ui-avatars.com/api/?background=fff&color=039be5&name=" + encodeURIComponent(session.user.name || "")} alt="" />
 | 
			
		||||
                                                </button>
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                            {
 | 
			
		||||
                                                profileDropdownExpanded && (
 | 
			
		||||
                                                    <div className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-50" role="menu" aria-orientation="vertical" aria-labelledby="user-menu">
 | 
			
		||||
                                                        <Link href={"/" + session.user.username}><a target="_blank" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Your Public Page</a></Link>
 | 
			
		||||
                                                        <Link href="/settings/profile"><a className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Your Profile</a></Link>
 | 
			
		||||
                                                        <Link href="/settings/password"><a className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Login & Security</a></Link>
 | 
			
		||||
                                                        <button onClick={logoutHandler} className="w-full text-left block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem">Sign out</button>
 | 
			
		||||
                                                    </div>
 | 
			
		||||
                                                )
 | 
			
		||||
                                            }
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className="-mr-2 flex md:hidden">
 | 
			
		||||
                                    <button onClick={toggleMobileMenu} type="button" className=" inline-flex items-center justify-center p-2 rounded-md text-white focus:outline-none" aria-controls="mobile-menu" aria-expanded="false">
 | 
			
		||||
                                        <span className="sr-only">Open main menu</span>
 | 
			
		||||
                                        { !mobileMenuExpanded && <MenuIcon className="block h-6 w-6" /> }
 | 
			
		||||
                                        { mobileMenuExpanded && <XIcon className="block h-6 w-6" /> }
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      <Link href="/availability">
 | 
			
		||||
                        <a
 | 
			
		||||
                          className={
 | 
			
		||||
                            router.pathname.startsWith("/availability")
 | 
			
		||||
                              ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                              : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                          }>
 | 
			
		||||
                          Availability
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </Link>
 | 
			
		||||
                      <Link href="/integrations">
 | 
			
		||||
                        <a
 | 
			
		||||
                          className={
 | 
			
		||||
                            router.pathname.startsWith("/integrations")
 | 
			
		||||
                              ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                              : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                          }>
 | 
			
		||||
                          Integrations
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </Link>
 | 
			
		||||
                      <Link href="/settings/profile">
 | 
			
		||||
                        <a
 | 
			
		||||
                          className={
 | 
			
		||||
                            router.pathname.startsWith("/settings")
 | 
			
		||||
                              ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                              : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"
 | 
			
		||||
                          }>
 | 
			
		||||
                          Settings
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </Link>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    { mobileMenuExpanded && <div className="border-b border-blue-500 md:hidden bg-blue-600" id="mobile-menu">
 | 
			
		||||
                        <div className="px-2 py-3 space-y-1 sm:px-3">
 | 
			
		||||
                            <Link href="/">
 | 
			
		||||
                                <a className={router.pathname == "/" ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium" : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"}>Dashboard</a>
 | 
			
		||||
                            </Link>
 | 
			
		||||
                            <Link href="/availability">
 | 
			
		||||
                                <a className={router.pathname.startsWith("/availability") ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium" : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"}>Availability</a>
 | 
			
		||||
                            </Link>
 | 
			
		||||
                            <Link href="/integrations">
 | 
			
		||||
                                <a className={router.pathname.startsWith("/integrations") ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium" : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"}>Integrations</a>
 | 
			
		||||
                            </Link>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div className="pt-4 pb-3 border-t border-blue-500">
 | 
			
		||||
                            <div className="flex items-center px-5">
 | 
			
		||||
                                <div className="flex-shrink-0">
 | 
			
		||||
                                    <img className="h-10 w-10 rounded-full" src={"https://eu.ui-avatars.com/api/?background=039be5&color=fff&name=" + encodeURIComponent(session.user.name || session.user.username)} alt="" />
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className="ml-3">
 | 
			
		||||
                                    <div className="text-base font-medium leading-none text-white">{session.user.name || session.user.username}</div>
 | 
			
		||||
                                    <div className="text-sm font-medium leading-none text-gray-200">{session.user.email}</div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div className="mt-3 px-2 space-y-1">
 | 
			
		||||
                                <Link href="/settings/profile">
 | 
			
		||||
                                    <a className="block px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">Your Profile</a>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                                <Link href="/settings">
 | 
			
		||||
                                    <a className={router.pathname.startsWith("/settings") ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium" : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"}>Settings</a>
 | 
			
		||||
                                </Link>
 | 
			
		||||
                                <button onClick={logoutHandler} className="block w-full text-left px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">Sign out</button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    }
 | 
			
		||||
                </nav>
 | 
			
		||||
                <header className={props.noPaddingBottom ? "pt-10" : "py-10"}>
 | 
			
		||||
                    <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 | 
			
		||||
                        <h1 className="text-3xl font-bold text-white">
 | 
			
		||||
                            {props.heading}
 | 
			
		||||
                        </h1>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </header>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <main className="-mt-32">
 | 
			
		||||
                <div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
 | 
			
		||||
                    {props.children}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </main>
 | 
			
		||||
        </div>
 | 
			
		||||
);
 | 
			
		||||
                <div className="hidden md:block">
 | 
			
		||||
                  <div className="ml-4 flex items-center md:ml-6">
 | 
			
		||||
                    <div className="ml-3 relative">
 | 
			
		||||
                      <div>
 | 
			
		||||
                        <button
 | 
			
		||||
                          onClick={toggleProfileDropdown}
 | 
			
		||||
                          type="button"
 | 
			
		||||
                          className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white"
 | 
			
		||||
                          id="user-menu"
 | 
			
		||||
                          aria-expanded="false"
 | 
			
		||||
                          aria-haspopup="true">
 | 
			
		||||
                          <span className="sr-only">Open user menu</span>
 | 
			
		||||
                          <img
 | 
			
		||||
                            className="h-8 w-8 rounded-full"
 | 
			
		||||
                            src={
 | 
			
		||||
                              session.user.image
 | 
			
		||||
                                ? session.user.image
 | 
			
		||||
                                : "https://eu.ui-avatars.com/api/?background=fff&color=039be5&name=" +
 | 
			
		||||
                                  encodeURIComponent(session.user.name || "")
 | 
			
		||||
                            }
 | 
			
		||||
                            alt=""
 | 
			
		||||
                          />
 | 
			
		||||
                        </button>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      {profileDropdownExpanded && (
 | 
			
		||||
                        <div
 | 
			
		||||
                          className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-50"
 | 
			
		||||
                          role="menu"
 | 
			
		||||
                          aria-orientation="vertical"
 | 
			
		||||
                          aria-labelledby="user-menu">
 | 
			
		||||
                          <Link href={"/" + session.user.username}>
 | 
			
		||||
                            <a
 | 
			
		||||
                              target="_blank"
 | 
			
		||||
                              className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
 | 
			
		||||
                              role="menuitem">
 | 
			
		||||
                              Your Public Page
 | 
			
		||||
                            </a>
 | 
			
		||||
                          </Link>
 | 
			
		||||
                          <Link href="/settings/profile">
 | 
			
		||||
                            <a
 | 
			
		||||
                              className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
 | 
			
		||||
                              role="menuitem">
 | 
			
		||||
                              Your Profile
 | 
			
		||||
                            </a>
 | 
			
		||||
                          </Link>
 | 
			
		||||
                          <Link href="/settings/password">
 | 
			
		||||
                            <a
 | 
			
		||||
                              className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
 | 
			
		||||
                              role="menuitem">
 | 
			
		||||
                              Login & Security
 | 
			
		||||
                            </a>
 | 
			
		||||
                          </Link>
 | 
			
		||||
                          <button
 | 
			
		||||
                            onClick={logoutHandler}
 | 
			
		||||
                            className="w-full text-left block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
 | 
			
		||||
                            role="menuitem">
 | 
			
		||||
                            Sign out
 | 
			
		||||
                          </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="-mr-2 flex md:hidden">
 | 
			
		||||
                  <button
 | 
			
		||||
                    onClick={toggleMobileMenu}
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className=" inline-flex items-center justify-center p-2 rounded-md text-white focus:outline-none"
 | 
			
		||||
                    aria-controls="mobile-menu"
 | 
			
		||||
                    aria-expanded="false">
 | 
			
		||||
                    <span className="sr-only">Open main menu</span>
 | 
			
		||||
                    {!mobileMenuExpanded && <MenuIcon className="block h-6 w-6" />}
 | 
			
		||||
                    {mobileMenuExpanded && <XIcon className="block h-6 w-6" />}
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          {mobileMenuExpanded && (
 | 
			
		||||
            <div className="border-b border-blue-500 md:hidden bg-blue-600" id="mobile-menu">
 | 
			
		||||
              <div className="px-2 py-3 space-y-1 sm:px-3">
 | 
			
		||||
                <Link href="/">
 | 
			
		||||
                  <a
 | 
			
		||||
                    className={
 | 
			
		||||
                      router.pathname == "/"
 | 
			
		||||
                        ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                        : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                    }>
 | 
			
		||||
                    Dashboard
 | 
			
		||||
                  </a>
 | 
			
		||||
                </Link>
 | 
			
		||||
                <Link href="/availability">
 | 
			
		||||
                  <a
 | 
			
		||||
                    className={
 | 
			
		||||
                      router.pathname.startsWith("/availability")
 | 
			
		||||
                        ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                        : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                    }>
 | 
			
		||||
                    Availability
 | 
			
		||||
                  </a>
 | 
			
		||||
                </Link>
 | 
			
		||||
                <Link href="/integrations">
 | 
			
		||||
                  <a
 | 
			
		||||
                    className={
 | 
			
		||||
                      router.pathname.startsWith("/integrations")
 | 
			
		||||
                        ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                        : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                    }>
 | 
			
		||||
                    Integrations
 | 
			
		||||
                  </a>
 | 
			
		||||
                </Link>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div className="pt-4 pb-3 border-t border-blue-500">
 | 
			
		||||
                <div className="flex items-center px-5">
 | 
			
		||||
                  <div className="flex-shrink-0">
 | 
			
		||||
                    <img
 | 
			
		||||
                      className="h-10 w-10 rounded-full"
 | 
			
		||||
                      src={
 | 
			
		||||
                        "https://eu.ui-avatars.com/api/?background=039be5&color=fff&name=" +
 | 
			
		||||
                        encodeURIComponent(session.user.name || session.user.username)
 | 
			
		||||
                      }
 | 
			
		||||
                      alt=""
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div className="ml-3">
 | 
			
		||||
                    <div className="text-base font-medium leading-none text-white">
 | 
			
		||||
                      {session.user.name || session.user.username}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div className="text-sm font-medium leading-none text-gray-200">{session.user.email}</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="mt-3 px-2 space-y-1">
 | 
			
		||||
                  <Link href="/settings/profile">
 | 
			
		||||
                    <a className="block px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">
 | 
			
		||||
                      Your Profile
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </Link>
 | 
			
		||||
                  <Link href="/settings">
 | 
			
		||||
                    <a
 | 
			
		||||
                      className={
 | 
			
		||||
                        router.pathname.startsWith("/settings")
 | 
			
		||||
                          ? "bg-blue-500 text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                          : "text-gray-100 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
 | 
			
		||||
                      }>
 | 
			
		||||
                      Settings
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </Link>
 | 
			
		||||
                  <button
 | 
			
		||||
                    onClick={logoutHandler}
 | 
			
		||||
                    className="block w-full text-left px-3 py-2 rounded-md text-base font-medium text-gray-100 hover:text-white hover:bg-gray-700">
 | 
			
		||||
                    Sign out
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          )}
 | 
			
		||||
        </nav>
 | 
			
		||||
        <header className={props.noPaddingBottom ? "pt-10" : "py-10"}>
 | 
			
		||||
          <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 | 
			
		||||
            <h1 className="text-3xl font-bold text-white">{props.heading}</h1>
 | 
			
		||||
          </div>
 | 
			
		||||
        </header>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <main className="-mt-32">
 | 
			
		||||
        <div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">{props.children}</div>
 | 
			
		||||
      </main>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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?
 | 
			
		||||
  <div>
 | 
			
		||||
    <label htmlFor="username" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
| 
						 | 
				
			
			@ -10,8 +10,20 @@ export const UsernameInput = React.forwardRef( (props, ref) => (
 | 
			
		|||
      <span className="bg-gray-50 border border-r-0 border-gray-300 rounded-l-md px-3 inline-flex items-center text-gray-500 sm:text-sm">
 | 
			
		||||
        {typeof window !== "undefined" && window.location.hostname}/
 | 
			
		||||
      </span>
 | 
			
		||||
      <input ref={ref} type="text" name="username" id="username" autoComplete="username" required {...props}
 | 
			
		||||
             className="focus:ring-blue-500 focus:border-blue-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"/>
 | 
			
		||||
      <input
 | 
			
		||||
        ref={ref}
 | 
			
		||||
        type="text"
 | 
			
		||||
        name="username"
 | 
			
		||||
        id="username"
 | 
			
		||||
        autoComplete="username"
 | 
			
		||||
        required
 | 
			
		||||
        {...props}
 | 
			
		||||
        className="focus:ring-blue-500 focus:border-blue-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300 lowercase"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
));
 | 
			
		||||
 | 
			
		||||
UsernameInput.displayName = "UsernameInput";
 | 
			
		||||
 | 
			
		||||
export { UsernameInput };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<HTMLInputElement>();
 | 
			
		||||
    const nameRef = useRef<HTMLInputElement>();
 | 
			
		||||
    const descriptionRef = useRef<HTMLTextAreaElement>();
 | 
			
		||||
    const avatarRef = useRef<HTMLInputElement>();
 | 
			
		||||
  const [successModalOpen, setSuccessModalOpen] = useState(false);
 | 
			
		||||
  const usernameRef = useRef<HTMLInputElement>();
 | 
			
		||||
  const nameRef = useRef<HTMLInputElement>();
 | 
			
		||||
  const descriptionRef = useRef<HTMLTextAreaElement>();
 | 
			
		||||
  const avatarRef = useRef<HTMLInputElement>();
 | 
			
		||||
  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 <p className="text-gray-400">Loading...</p>;
 | 
			
		||||
  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 (
 | 
			
		||||
    <Shell heading="Profile">
 | 
			
		||||
      <Head>
 | 
			
		||||
        <title>Profile | Calendso</title>
 | 
			
		||||
        <link rel="icon" href="/favicon.ico" />
 | 
			
		||||
      </Head>
 | 
			
		||||
      <SettingsShell>
 | 
			
		||||
        <form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
 | 
			
		||||
          {hasErrors && <ErrorAlert message={errorMessage} />}
 | 
			
		||||
          <div className="py-6 px-4 sm:p-6 lg:pb-8">
 | 
			
		||||
            <div>
 | 
			
		||||
              <h2 className="text-lg leading-6 font-medium text-gray-900">Profile</h2>
 | 
			
		||||
              <p className="mt-1 text-sm text-gray-500">Review and change your public page details.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
            <div className="mt-6 flex flex-col lg:flex-row">
 | 
			
		||||
              <div className="flex-grow space-y-6">
 | 
			
		||||
                <div className="flex">
 | 
			
		||||
                  <div className="w-1/2 mr-2">
 | 
			
		||||
                    <UsernameInput ref={usernameRef} defaultValue={props.user.username} />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div className="w-1/2 ml-2">
 | 
			
		||||
                    <label htmlFor="name" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                      Full name
 | 
			
		||||
                    </label>
 | 
			
		||||
                    <input
 | 
			
		||||
                      ref={nameRef}
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      name="name"
 | 
			
		||||
                      id="name"
 | 
			
		||||
                      autoComplete="given-name"
 | 
			
		||||
                      placeholder="Your name"
 | 
			
		||||
                      required
 | 
			
		||||
                      className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
 | 
			
		||||
                      defaultValue={props.user.name}
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
    return(
 | 
			
		||||
        <Shell heading="Profile">
 | 
			
		||||
            <Head>
 | 
			
		||||
                <title>Profile | Calendso</title>
 | 
			
		||||
                <link rel="icon" href="/favicon.ico" />
 | 
			
		||||
            </Head>
 | 
			
		||||
            <SettingsShell>
 | 
			
		||||
                <form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
 | 
			
		||||
                    {hasErrors && <ErrorAlert message={errorMessage} />}
 | 
			
		||||
                    <div className="py-6 px-4 sm:p-6 lg:pb-8">
 | 
			
		||||
                        <div>
 | 
			
		||||
                            <h2 className="text-lg leading-6 font-medium text-gray-900">Profile</h2>
 | 
			
		||||
                            <p className="mt-1 text-sm text-gray-500">
 | 
			
		||||
                                Review and change your public page details.
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <label htmlFor="about" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                    About
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <div className="mt-1">
 | 
			
		||||
                    <textarea
 | 
			
		||||
                      ref={descriptionRef}
 | 
			
		||||
                      id="about"
 | 
			
		||||
                      name="about"
 | 
			
		||||
                      placeholder="A little something about yourself."
 | 
			
		||||
                      rows={3}
 | 
			
		||||
                      className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
 | 
			
		||||
                      {props.user.bio}
 | 
			
		||||
                    </textarea>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                    Timezone
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <div className="mt-1">
 | 
			
		||||
                    <TimezoneSelect
 | 
			
		||||
                      id="timeZone"
 | 
			
		||||
                      value={selectedTimeZone}
 | 
			
		||||
                      onChange={setSelectedTimeZone}
 | 
			
		||||
                      className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div>
 | 
			
		||||
                  <label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                    First Day of Week
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <div className="mt-1">
 | 
			
		||||
                    <select
 | 
			
		||||
                      id="weekStart"
 | 
			
		||||
                      value={selectedWeekStartDay}
 | 
			
		||||
                      onChange={(e) => setSelectedWeekStartDay(e.target.value)}
 | 
			
		||||
                      className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
 | 
			
		||||
                      <option value="Sunday">Sunday</option>
 | 
			
		||||
                      <option value="Monday">Monday</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
                        <div className="mt-6 flex flex-col lg:flex-row">
 | 
			
		||||
                            <div className="flex-grow space-y-6">
 | 
			
		||||
                                <div className="flex">
 | 
			
		||||
                                    <div className="w-1/2 mr-2">
 | 
			
		||||
                                        <UsernameInput ref={usernameRef} defaultValue={props.user.username} />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                    <div className="w-1/2 ml-2">
 | 
			
		||||
                                        <label htmlFor="name" className="block text-sm font-medium text-gray-700">Full name</label>
 | 
			
		||||
                                        <input ref={nameRef} type="text" name="name" id="name" autoComplete="given-name" placeholder="Your name" required className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" defaultValue={props.user.name} />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <label htmlFor="about" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                                        About
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <div className="mt-1">
 | 
			
		||||
                                        <textarea ref={descriptionRef} id="about" name="about" placeholder="A little something about yourself." rows={3} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">{props.user.bio}</textarea>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                                        Timezone
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <div className="mt-1">
 | 
			
		||||
                                        <TimezoneSelect id="timeZone" value={selectedTimeZone} onChange={setSelectedTimeZone} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md" />
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div>
 | 
			
		||||
                                    <label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                                        First Day of Week
 | 
			
		||||
                                    </label>
 | 
			
		||||
                                    <div className="mt-1">
 | 
			
		||||
                                        <select id="weekStart" value={selectedWeekStartDay} onChange={e => setSelectedWeekStartDay(e.target.value)} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
 | 
			
		||||
                                            <option value="Sunday">Sunday</option>
 | 
			
		||||
                                            <option value="Monday">Monday</option>
 | 
			
		||||
                                        </select>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
 | 
			
		||||
                                <div className="mt-6 flex-grow lg:mt-0 lg:ml-6 lg:flex-grow-0 lg:flex-shrink-0">
 | 
			
		||||
                                <p className="mb-2 text-sm font-medium text-gray-700" aria-hidden="true">
 | 
			
		||||
                                    Photo
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <div className="mt-1 lg:hidden">
 | 
			
		||||
                                    <div className="flex items-center">
 | 
			
		||||
                                        <div className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12" aria-hidden="true">
 | 
			
		||||
                                            <Avatar user={props.user} className="rounded-full h-full w-full" />
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                        {/* <div className="ml-5 rounded-md shadow-sm">
 | 
			
		||||
              <div className="mt-6 flex-grow lg:mt-0 lg:ml-6 lg:flex-grow-0 lg:flex-shrink-0">
 | 
			
		||||
                <p className="mb-2 text-sm font-medium text-gray-700" aria-hidden="true">
 | 
			
		||||
                  Photo
 | 
			
		||||
                </p>
 | 
			
		||||
                <div className="mt-1 lg:hidden">
 | 
			
		||||
                  <div className="flex items-center">
 | 
			
		||||
                    <div
 | 
			
		||||
                      className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
 | 
			
		||||
                      aria-hidden="true">
 | 
			
		||||
                      <Avatar user={props.user} className="rounded-full h-full w-full" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {/* <div className="ml-5 rounded-md shadow-sm">
 | 
			
		||||
                                            <div className="group relative border border-gray-300 rounded-md py-2 px-3 flex items-center justify-center hover:bg-gray-50 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500">
 | 
			
		||||
                                                <label htmlFor="user_photo" className="relative text-sm leading-4 font-medium text-gray-700 pointer-events-none">
 | 
			
		||||
                                                    <span>Change</span>
 | 
			
		||||
| 
						 | 
				
			
			@ -143,64 +176,81 @@ export default function Settings(props) {
 | 
			
		|||
                                                <input id="user_photo" name="user_photo" type="file" className="absolute w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
 | 
			
		||||
                                            </div>
 | 
			
		||||
                                        </div> */}
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                                <div className="hidden relative rounded-full overflow-hidden lg:block">
 | 
			
		||||
                                    <Avatar
 | 
			
		||||
                                        user={props.user}
 | 
			
		||||
                                        className="relative rounded-full w-40 h-40"
 | 
			
		||||
                                        fallback={<div className="relative bg-blue-600 rounded-full w-40 h-40"></div>}
 | 
			
		||||
                                    />
 | 
			
		||||
                                    {/* <label htmlFor="user-photo" className="absolute inset-0 w-full h-full bg-black bg-opacity-75 flex items-center justify-center text-sm font-medium text-white opacity-0 hover:opacity-100 focus-within:opacity-100">
 | 
			
		||||
                <div className="hidden relative rounded-full overflow-hidden lg:block">
 | 
			
		||||
                  <Avatar
 | 
			
		||||
                    user={props.user}
 | 
			
		||||
                    className="relative rounded-full w-40 h-40"
 | 
			
		||||
                    fallback={<div className="relative bg-blue-600 rounded-full w-40 h-40"></div>}
 | 
			
		||||
                  />
 | 
			
		||||
                  {/* <label htmlFor="user-photo" className="absolute inset-0 w-full h-full bg-black bg-opacity-75 flex items-center justify-center text-sm font-medium text-white opacity-0 hover:opacity-100 focus-within:opacity-100">
 | 
			
		||||
                                        <span>Change</span>
 | 
			
		||||
                                        <span className="sr-only"> user photo</span>
 | 
			
		||||
                                        <input type="file" id="user-photo" name="user-photo" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
 | 
			
		||||
                                    </label> */}
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div className="mt-4">
 | 
			
		||||
                                    <label htmlFor="avatar" className="block text-sm font-medium text-gray-700">Avatar URL</label>
 | 
			
		||||
                                    <input ref={avatarRef} type="text" name="avatar" id="avatar" placeholder="URL" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" defaultValue={props.user.avatar} />
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <hr className="mt-8" />
 | 
			
		||||
                        <div className="py-4 flex justify-end">
 | 
			
		||||
                            <button type="submit" className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
 | 
			
		||||
                                Save
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </form>
 | 
			
		||||
                <Modal heading="Profile updated successfully" description="Your user profile has been updated successfully." open={successModalOpen} handleClose={closeSuccessModal} />
 | 
			
		||||
            </SettingsShell>
 | 
			
		||||
        </Shell>
 | 
			
		||||
    );
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="mt-4">
 | 
			
		||||
                  <label htmlFor="avatar" className="block text-sm font-medium text-gray-700">
 | 
			
		||||
                    Avatar URL
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <input
 | 
			
		||||
                    ref={avatarRef}
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    name="avatar"
 | 
			
		||||
                    id="avatar"
 | 
			
		||||
                    placeholder="URL"
 | 
			
		||||
                    className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
 | 
			
		||||
                    defaultValue={props.user.avatar}
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <hr className="mt-8" />
 | 
			
		||||
            <div className="py-4 flex justify-end">
 | 
			
		||||
              <button
 | 
			
		||||
                type="submit"
 | 
			
		||||
                className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
 | 
			
		||||
                Save
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </form>
 | 
			
		||||
        <Modal
 | 
			
		||||
          heading="Profile updated successfully"
 | 
			
		||||
          description="Your user profile has been updated successfully."
 | 
			
		||||
          open={successModalOpen}
 | 
			
		||||
          handleClose={closeSuccessModal}
 | 
			
		||||
        />
 | 
			
		||||
      </SettingsShell>
 | 
			
		||||
    </Shell>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
  return {
 | 
			
		||||
    props: { user }, // will be passed to the page component as props
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue