Merge pull request #354 from emrysal/feature/user-theme
Implemented theme through user preferences
This commit is contained in:
commit
6ed9bfde7b
10 changed files with 647 additions and 577 deletions
17
components/Theme.tsx
Normal file
17
components/Theme.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export default function Theme(theme?: string) {
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!theme && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.add(theme);
|
||||||
|
}
|
||||||
|
setIsReady(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isReady,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
import { GetServerSideProps } from "next";
|
import { GetServerSideProps } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import prisma from "../lib/prisma";
|
import prisma, { whereAndSelect } from "@lib/prisma";
|
||||||
import Avatar from "../components/Avatar";
|
import Avatar from "../components/Avatar";
|
||||||
|
import Theme from "@components/Theme";
|
||||||
|
|
||||||
export default function User(props): User {
|
export default function User(props): User {
|
||||||
|
const { isReady } = Theme(props.user.theme);
|
||||||
|
|
||||||
const eventTypes = props.eventTypes.map((type) => (
|
const eventTypes = props.eventTypes.map((type) => (
|
||||||
<li
|
<li
|
||||||
key={type.id}
|
key={type.id}
|
||||||
|
@ -21,6 +24,7 @@ export default function User(props): User {
|
||||||
</li>
|
</li>
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
|
isReady && (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>{props.user.name || props.user.username} | Calendso</title>
|
<title>{props.user.name || props.user.username} | Calendso</title>
|
||||||
|
@ -46,25 +50,18 @@ export default function User(props): User {
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const user = await prisma.user.findFirst({
|
const user = await whereAndSelect(
|
||||||
where: {
|
prisma.user.findFirst,
|
||||||
|
{
|
||||||
username: context.query.user.toLowerCase(),
|
username: context.query.user.toLowerCase(),
|
||||||
},
|
},
|
||||||
select: {
|
["id", "username", "email", "name", "bio", "avatar", "eventTypes", "theme"]
|
||||||
id: true,
|
);
|
||||||
username: true,
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
bio: true,
|
|
||||||
avatar: true,
|
|
||||||
eventTypes: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
|
|
|
@ -13,12 +13,15 @@ import Avatar from "../../components/Avatar";
|
||||||
import { timeZone } from "../../lib/clock";
|
import { timeZone } from "../../lib/clock";
|
||||||
import DatePicker from "../../components/booking/DatePicker";
|
import DatePicker from "../../components/booking/DatePicker";
|
||||||
import PoweredByCalendso from "../../components/ui/PoweredByCalendso";
|
import PoweredByCalendso from "../../components/ui/PoweredByCalendso";
|
||||||
|
import Theme from "@components/Theme";
|
||||||
|
|
||||||
export default function Type(props): Type {
|
export default function Type(props): Type {
|
||||||
// Get router variables
|
// Get router variables
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { rescheduleUid } = router.query;
|
const { rescheduleUid } = router.query;
|
||||||
|
|
||||||
|
const { isReady } = Theme(props.user.theme);
|
||||||
|
|
||||||
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
||||||
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
||||||
const [timeFormat, setTimeFormat] = useState("h:mma");
|
const [timeFormat, setTimeFormat] = useState("h:mma");
|
||||||
|
@ -46,11 +49,12 @@ export default function Type(props): Type {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
isReady && (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
{rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username} |
|
{rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username}{" "}
|
||||||
Calendso
|
| Calendso
|
||||||
</title>
|
</title>
|
||||||
<meta name="title" content={"Meet " + (props.user.name || props.user.username) + " via Calendso"} />
|
<meta name="title" content={"Meet " + (props.user.name || props.user.username) + " via Calendso"} />
|
||||||
<meta name="description" content={props.eventType.description} />
|
<meta name="description" content={props.eventType.description} />
|
||||||
|
@ -152,6 +156,7 @@ export default function Type(props): Type {
|
||||||
{!props.user.hideBranding && <PoweredByCalendso />}
|
{!props.user.hideBranding && <PoweredByCalendso />}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +180,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
"weekStart",
|
"weekStart",
|
||||||
"availability",
|
"availability",
|
||||||
"hideBranding",
|
"hideBranding",
|
||||||
|
"theme",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { CalendarIcon, ClockIcon, ExclamationIcon, LocationMarkerIcon } from "@heroicons/react/solid";
|
import { CalendarIcon, ClockIcon, ExclamationIcon, LocationMarkerIcon } from "@heroicons/react/solid";
|
||||||
import prisma from "../../lib/prisma";
|
import prisma, { whereAndSelect } from "../../lib/prisma";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
@ -14,6 +14,7 @@ import { LocationType } from "../../lib/location";
|
||||||
import Avatar from "../../components/Avatar";
|
import Avatar from "../../components/Avatar";
|
||||||
import Button from "../../components/ui/Button";
|
import Button from "../../components/ui/Button";
|
||||||
import { EventTypeCustomInputType } from "../../lib/eventTypeInput";
|
import { EventTypeCustomInputType } from "../../lib/eventTypeInput";
|
||||||
|
import Theme from "@components/Theme";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
|
@ -32,7 +33,10 @@ export default function Book(props: any): JSX.Element {
|
||||||
const [selectedLocation, setSelectedLocation] = useState<LocationType>(
|
const [selectedLocation, setSelectedLocation] = useState<LocationType>(
|
||||||
locations.length === 1 ? locations[0].type : ""
|
locations.length === 1 ? locations[0].type : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { isReady } = Theme(props.user.theme);
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPreferredTimeZone(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess());
|
setPreferredTimeZone(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess());
|
||||||
setIs24h(!!localStorage.getItem("timeOption.is24hClock"));
|
setIs24h(!!localStorage.getItem("timeOption.is24hClock"));
|
||||||
|
@ -138,6 +142,7 @@ export default function Book(props: any): JSX.Element {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
isReady && (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
|
@ -194,7 +199,9 @@ export default function Book(props: any): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label htmlFor="email" className="block text-sm font-medium dark:text-white text-gray-700">
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
className="block text-sm font-medium dark:text-white text-gray-700">
|
||||||
Email address
|
Email address
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
|
@ -300,7 +307,9 @@ export default function Book(props: any): JSX.Element {
|
||||||
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
className="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/>
|
||||||
<label htmlFor={input.label} className="block text-sm font-medium text-gray-700">
|
<label
|
||||||
|
htmlFor={input.label}
|
||||||
|
className="block text-sm font-medium text-gray-700">
|
||||||
{input.label}
|
{input.label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -362,23 +371,18 @@ export default function Book(props: any): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSideProps(context) {
|
export async function getServerSideProps(context) {
|
||||||
const user = await prisma.user.findFirst({
|
const user = await whereAndSelect(
|
||||||
where: {
|
prisma.user.findFirst,
|
||||||
|
{
|
||||||
username: context.query.user,
|
username: context.query.user,
|
||||||
},
|
},
|
||||||
select: {
|
["username", "name", "email", "bio", "avatar", "eventTypes", "theme"]
|
||||||
username: true,
|
);
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
bio: true,
|
|
||||||
avatar: true,
|
|
||||||
eventTypes: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventType = await prisma.eventType.findUnique({
|
const eventType = await prisma.eventType.findUnique({
|
||||||
where: {
|
where: {
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getSession } from 'next-auth/client';
|
import { getSession } from "next-auth/client";
|
||||||
import prisma from '../../../lib/prisma';
|
import prisma, { whereAndSelect } from "@lib/prisma";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const session = await getSession({req: req});
|
const session = await getSession({ req: req });
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
res.status(401).json({message: "Not authenticated"});
|
res.status(401).json({ message: "Not authenticated" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user
|
// Get user
|
||||||
const user = await prisma.user.findUnique({
|
const user = await whereAndSelect(
|
||||||
where: {
|
prisma.user.findUnique,
|
||||||
email: session.user.email,
|
{
|
||||||
|
id: session.user.id,
|
||||||
},
|
},
|
||||||
select: {
|
["id", "password"]
|
||||||
id: true,
|
);
|
||||||
password: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) { res.status(404).json({message: 'User not found'}); return; }
|
if (!user) {
|
||||||
|
res.status(404).json({ message: "User not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const username = req.body.username;
|
const username = req.body.username;
|
||||||
// username is changed: username is optional but it is necessary to be unique, enforce here
|
// username is changed: username is optional but it is necessary to be unique, enforce here
|
||||||
|
@ -29,10 +30,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const userConflict = await prisma.user.findFirst({
|
const userConflict = await prisma.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
username,
|
username,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
if (userConflict) {
|
if (userConflict) {
|
||||||
return res.status(409).json({ message: 'Username already taken' });
|
return res.status(409).json({ message: "Username already taken" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +43,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
const timeZone = req.body.timeZone;
|
const timeZone = req.body.timeZone;
|
||||||
const weekStart = req.body.weekStart;
|
const weekStart = req.body.weekStart;
|
||||||
const hideBranding = req.body.hideBranding;
|
const hideBranding = req.body.hideBranding;
|
||||||
|
const theme = req.body.theme;
|
||||||
|
|
||||||
const updateUser = await prisma.user.update({
|
await prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
},
|
},
|
||||||
|
@ -52,11 +54,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
bio: description,
|
bio: description,
|
||||||
timeZone: timeZone,
|
timeZone,
|
||||||
weekStart: weekStart,
|
weekStart,
|
||||||
hideBranding: hideBranding,
|
hideBranding,
|
||||||
|
theme,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).json({message: 'Profile updated successfully'});
|
return res.status(200).json({ message: "Profile updated successfully" });
|
||||||
}
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
import { GetServerSideProps } from "next";
|
import { GetServerSideProps } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import prisma from "../../lib/prisma";
|
import prisma, { whereAndSelect } from "@lib/prisma";
|
||||||
import Modal from "../../components/Modal";
|
import Modal from "../../components/Modal";
|
||||||
import Shell from "../../components/Shell";
|
import Shell from "../../components/Shell";
|
||||||
import SettingsShell from "../../components/Settings";
|
import SettingsShell from "../../components/Settings";
|
||||||
import Avatar from "../../components/Avatar";
|
import Avatar from "../../components/Avatar";
|
||||||
import { getSession } from "next-auth/client";
|
import { getSession } from "next-auth/client";
|
||||||
|
import Select from "react-select";
|
||||||
import TimezoneSelect from "react-timezone-select";
|
import TimezoneSelect from "react-timezone-select";
|
||||||
import { UsernameInput } from "../../components/ui/UsernameInput";
|
import { UsernameInput } from "../../components/ui/UsernameInput";
|
||||||
import ErrorAlert from "../../components/ui/alerts/Error";
|
import ErrorAlert from "../../components/ui/alerts/Error";
|
||||||
|
@ -18,12 +19,25 @@ export default function Settings(props) {
|
||||||
const descriptionRef = useRef<HTMLTextAreaElement>();
|
const descriptionRef = useRef<HTMLTextAreaElement>();
|
||||||
const avatarRef = useRef<HTMLInputElement>();
|
const avatarRef = useRef<HTMLInputElement>();
|
||||||
const hideBrandingRef = useRef<HTMLInputElement>();
|
const hideBrandingRef = useRef<HTMLInputElement>();
|
||||||
|
const [selectedTheme, setSelectedTheme] = useState({ value: "" });
|
||||||
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
|
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
|
||||||
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState(props.user.weekStart || "Sunday");
|
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: "" });
|
||||||
|
|
||||||
const [hasErrors, setHasErrors] = useState(false);
|
const [hasErrors, setHasErrors] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
|
||||||
|
const themeOptions = [
|
||||||
|
{ value: "light", label: "Light" },
|
||||||
|
{ value: "dark", label: "Dark" },
|
||||||
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedTheme(
|
||||||
|
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null
|
||||||
|
);
|
||||||
|
setSelectedWeekStartDay({ value: props.user.weekStart, label: props.user.weekStart });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const closeSuccessModal = () => {
|
const closeSuccessModal = () => {
|
||||||
setSuccessModalOpen(false);
|
setSuccessModalOpen(false);
|
||||||
};
|
};
|
||||||
|
@ -43,7 +57,7 @@ export default function Settings(props) {
|
||||||
const enteredDescription = descriptionRef.current.value;
|
const enteredDescription = descriptionRef.current.value;
|
||||||
const enteredAvatar = avatarRef.current.value;
|
const enteredAvatar = avatarRef.current.value;
|
||||||
const enteredTimeZone = selectedTimeZone.value;
|
const enteredTimeZone = selectedTimeZone.value;
|
||||||
const enteredWeekStartDay = selectedWeekStartDay;
|
const enteredWeekStartDay = selectedWeekStartDay.value;
|
||||||
const enteredHideBranding = hideBrandingRef.current.checked;
|
const enteredHideBranding = hideBrandingRef.current.checked;
|
||||||
|
|
||||||
// TODO: Add validation
|
// TODO: Add validation
|
||||||
|
@ -58,6 +72,7 @@ export default function Settings(props) {
|
||||||
timeZone: enteredTimeZone,
|
timeZone: enteredTimeZone,
|
||||||
weekStart: enteredWeekStartDay,
|
weekStart: enteredWeekStartDay,
|
||||||
hideBranding: enteredHideBranding,
|
hideBranding: enteredHideBranding,
|
||||||
|
theme: selectedTheme ? selectedTheme.value : null,
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -124,9 +139,8 @@ export default function Settings(props) {
|
||||||
name="about"
|
name="about"
|
||||||
placeholder="A little something about yourself."
|
placeholder="A little something about yourself."
|
||||||
rows={3}
|
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">
|
defaultValue={props.user.bio}
|
||||||
{props.user.bio}
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"></textarea>
|
||||||
</textarea>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -147,14 +161,48 @@ export default function Settings(props) {
|
||||||
First Day of Week
|
First Day of Week
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<select
|
<Select
|
||||||
id="weekStart"
|
id="weekStart"
|
||||||
value={selectedWeekStartDay}
|
value={selectedWeekStartDay}
|
||||||
onChange={(e) => setSelectedWeekStartDay(e.target.value)}
|
onChange={setSelectedWeekStartDay}
|
||||||
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">
|
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>
|
options={[
|
||||||
<option value="Monday">Monday</option>
|
{ value: "Sunday", label: "Sunday" },
|
||||||
</select>
|
{ value: "Monday", label: "Monday" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="theme" className="block text-sm font-medium text-gray-700">
|
||||||
|
Single Theme
|
||||||
|
</label>
|
||||||
|
<div className="my-1">
|
||||||
|
<Select
|
||||||
|
id="theme"
|
||||||
|
isDisabled={!selectedTheme}
|
||||||
|
defaultValue={selectedTheme || themeOptions[0]}
|
||||||
|
onChange={setSelectedTheme}
|
||||||
|
className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||||
|
options={themeOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex items-start">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id="theme-adjust-os"
|
||||||
|
name="theme-adjust-os"
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(e) => setSelectedTheme(e.target.checked ? null : themeOptions[0])}
|
||||||
|
defaultChecked={!selectedTheme}
|
||||||
|
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="theme-adjust-os" className="font-medium text-gray-700">
|
||||||
|
Automatically adjust theme based on invitee preferences
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -257,22 +305,13 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
return { redirect: { permanent: false, destination: "/auth/login" } };
|
return { redirect: { permanent: false, destination: "/auth/login" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
const user = await whereAndSelect(
|
||||||
where: {
|
prisma.user.findFirst,
|
||||||
email: session.user.email,
|
{
|
||||||
|
id: session.user.id,
|
||||||
},
|
},
|
||||||
select: {
|
["id", "username", "name", "email", "bio", "avatar", "timeZone", "weekStart", "hideBranding", "theme"]
|
||||||
id: true,
|
);
|
||||||
username: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
bio: true,
|
|
||||||
avatar: true,
|
|
||||||
timeZone: true,
|
|
||||||
weekStart: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { user }, // will be passed to the page component as props
|
props: { user }, // will be passed to the page component as props
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import prisma from "../lib/prisma";
|
import prisma, { whereAndSelect } from "../lib/prisma";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { CheckIcon } from "@heroicons/react/outline";
|
import { CheckIcon } from "@heroicons/react/outline";
|
||||||
|
@ -11,6 +11,7 @@ import toArray from "dayjs/plugin/toArray";
|
||||||
import timezone from "dayjs/plugin/timezone";
|
import timezone from "dayjs/plugin/timezone";
|
||||||
import { createEvent } from "ics";
|
import { createEvent } from "ics";
|
||||||
import { getEventName } from "../lib/event";
|
import { getEventName } from "../lib/event";
|
||||||
|
import Theme from "@components/Theme";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(toArray);
|
dayjs.extend(toArray);
|
||||||
|
@ -22,6 +23,7 @@ export default function Success(props) {
|
||||||
|
|
||||||
const [is24h, setIs24h] = useState(false);
|
const [is24h, setIs24h] = useState(false);
|
||||||
const [date, setDate] = useState(dayjs.utc(router.query.date));
|
const [date, setDate] = useState(dayjs.utc(router.query.date));
|
||||||
|
const { isReady } = Theme(props.user.theme);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()));
|
setDate(date.tz(localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()));
|
||||||
|
@ -31,7 +33,7 @@ export default function Success(props) {
|
||||||
const eventName = getEventName(name, props.eventType.title, props.eventType.eventName);
|
const eventName = getEventName(name, props.eventType.title, props.eventType.eventName);
|
||||||
|
|
||||||
function eventLink(): string {
|
function eventLink(): string {
|
||||||
let optional = {};
|
const optional = {};
|
||||||
if (location) {
|
if (location) {
|
||||||
optional["location"] = location;
|
optional["location"] = location;
|
||||||
}
|
}
|
||||||
|
@ -57,6 +59,7 @@ export default function Success(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
isReady && (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Booking Confirmed | {eventName} | Calendso</title>
|
<title>Booking Confirmed | {eventName} | Calendso</title>
|
||||||
|
@ -208,36 +211,34 @@ export default function Success(props) {
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getServerSideProps(context) {
|
export async function getServerSideProps(context) {
|
||||||
const user = await prisma.user.findFirst({
|
const user = context.query.user
|
||||||
where: {
|
? await whereAndSelect(
|
||||||
|
prisma.user.findFirst,
|
||||||
|
{
|
||||||
username: context.query.user,
|
username: context.query.user,
|
||||||
},
|
},
|
||||||
select: {
|
["username", "name", "bio", "avatar", "eventTypes", "hideBranding", "theme"]
|
||||||
username: true,
|
)
|
||||||
name: true,
|
: null;
|
||||||
bio: true,
|
|
||||||
avatar: true,
|
|
||||||
eventTypes: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventType = await prisma.eventType.findUnique({
|
if (!user) {
|
||||||
where: {
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType = await whereAndSelect(
|
||||||
|
prisma.eventType.findUnique,
|
||||||
|
{
|
||||||
id: parseInt(context.query.type),
|
id: parseInt(context.query.type),
|
||||||
},
|
},
|
||||||
select: {
|
["id", "title", "description", "length", "eventName"]
|
||||||
id: true,
|
);
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
length: true,
|
|
||||||
eventName: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "theme" TEXT;
|
|
@ -50,6 +50,7 @@ model User {
|
||||||
endTime Int @default(1440)
|
endTime Int @default(1440)
|
||||||
bufferTime Int @default(0)
|
bufferTime Int @default(0)
|
||||||
hideBranding Boolean @default(false)
|
hideBranding Boolean @default(false)
|
||||||
|
theme String?
|
||||||
createdDate DateTime @default(now()) @map(name: "created")
|
createdDate DateTime @default(now()) @map(name: "created")
|
||||||
eventTypes EventType[]
|
eventTypes EventType[]
|
||||||
credentials Credential[]
|
credentials Credential[]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: "jit",
|
mode: "jit",
|
||||||
purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
|
purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
|
||||||
darkMode: "media",
|
darkMode: "class",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
|
|
Loading…
Reference in a new issue