2021-06-23 15:49:10 +00:00
|
|
|
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";
|
2021-06-09 12:26:00 +00:00
|
|
|
import ErrorAlert from "../../components/ui/alerts/Error";
|
2021-04-07 15:03:02 +00:00
|
|
|
|
|
|
|
export default function Settings(props) {
|
2021-06-23 15:49:10 +00:00
|
|
|
const [successModalOpen, setSuccessModalOpen] = useState(false);
|
|
|
|
const usernameRef = useRef<HTMLInputElement>();
|
|
|
|
const nameRef = useRef<HTMLInputElement>();
|
|
|
|
const descriptionRef = useRef<HTMLTextAreaElement>();
|
|
|
|
const avatarRef = useRef<HTMLInputElement>();
|
2021-06-29 16:08:55 +00:00
|
|
|
const hideBrandingRef = useRef<HTMLInputElement>();
|
2021-06-23 15:49:10 +00:00
|
|
|
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
|
|
|
|
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState(props.user.weekStart || "Sunday");
|
2021-04-07 15:03:02 +00:00
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
const [hasErrors, setHasErrors] = useState(false);
|
|
|
|
const [errorMessage, setErrorMessage] = useState("");
|
2021-04-20 12:56:50 +00:00
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
const closeSuccessModal = () => {
|
|
|
|
setSuccessModalOpen(false);
|
|
|
|
};
|
2021-06-09 12:26:00 +00:00
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
const handleError = async (resp) => {
|
|
|
|
if (!resp.ok) {
|
|
|
|
const error = await resp.json();
|
|
|
|
throw new Error(error.message);
|
2021-04-07 15:03:02 +00:00
|
|
|
}
|
2021-06-23 15:49:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
async function updateProfileHandler(event) {
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
|
|
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;
|
2021-06-29 16:08:55 +00:00
|
|
|
const enteredHideBranding = hideBrandingRef.current.checked;
|
2021-06-23 15:49:10 +00:00
|
|
|
|
|
|
|
// TODO: Add validation
|
|
|
|
|
|
|
|
await fetch("/api/user/profile", {
|
|
|
|
method: "PATCH",
|
|
|
|
body: JSON.stringify({
|
|
|
|
username: enteredUsername,
|
|
|
|
name: enteredName,
|
|
|
|
description: enteredDescription,
|
|
|
|
avatar: enteredAvatar,
|
|
|
|
timeZone: enteredTimeZone,
|
|
|
|
weekStart: enteredWeekStartDay,
|
2021-06-29 16:08:55 +00:00
|
|
|
hideBranding: enteredHideBranding,
|
2021-06-23 15:49:10 +00:00
|
|
|
}),
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.then(handleError)
|
|
|
|
.then(() => {
|
|
|
|
setSuccessModalOpen(true);
|
|
|
|
setHasErrors(false); // dismiss any open errors
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
setHasErrors(true);
|
|
|
|
setErrorMessage(err.message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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 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>
|
2021-04-07 15:03:02 +00:00
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
<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>
|
2021-06-29 16:08:55 +00:00
|
|
|
<div>
|
|
|
|
<div className="relative flex items-start">
|
|
|
|
<div className="flex items-center h-5">
|
|
|
|
<input
|
|
|
|
id="hide-branding"
|
|
|
|
name="hide-branding"
|
|
|
|
type="checkbox"
|
|
|
|
ref={hideBrandingRef}
|
|
|
|
defaultChecked={props.user.hideBranding}
|
|
|
|
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="ml-3 text-sm">
|
|
|
|
<label htmlFor="hide-branding" className="font-medium text-gray-700">
|
|
|
|
Disable Calendso branding
|
|
|
|
</label>
|
|
|
|
<p className="text-gray-500">Hide all Calendso branding from your public pages.</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2021-06-23 15:49:10 +00:00
|
|
|
</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">
|
2021-04-07 15:03:02 +00:00
|
|
|
<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>
|
|
|
|
<span className="sr-only"> user photo</span>
|
|
|
|
</label>
|
|
|
|
<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>
|
2021-04-29 12:36:37 +00:00
|
|
|
</div> */}
|
2021-06-23 15:49:10 +00:00
|
|
|
</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">
|
2021-04-07 15:03:02 +00:00
|
|
|
<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" />
|
2021-04-29 12:36:37 +00:00
|
|
|
</label> */}
|
2021-06-23 15:49:10 +00:00
|
|
|
</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>
|
|
|
|
);
|
2021-04-07 15:03:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
|
|
|
const session = await getSession(context);
|
|
|
|
if (!session) {
|
|
|
|
return { redirect: { permanent: false, destination: "/auth/login" } };
|
|
|
|
}
|
2021-04-07 15:03:02 +00:00
|
|
|
|
2021-06-23 15:49:10 +00:00
|
|
|
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,
|
2021-06-29 16:08:55 +00:00
|
|
|
hideBranding: true,
|
2021-06-23 15:49:10 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
props: { user }, // will be passed to the page component as props
|
|
|
|
};
|
|
|
|
};
|