Merge branch 'main' into add_booking_confirmed_webhook_event
commit
b000b970b5
|
@ -8,6 +8,7 @@ import {
|
|||
LinkIcon,
|
||||
LogoutIcon,
|
||||
PuzzleIcon,
|
||||
MoonIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
|
@ -356,14 +357,25 @@ function UserDropdown({ small }: { small?: boolean }) {
|
|||
const { t } = useLocale();
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
const mutation = trpc.useMutation("viewer.away");
|
||||
const utils = trpc.useContext();
|
||||
|
||||
return (
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="flex items-center w-full space-x-2 cursor-pointer group">
|
||||
<span
|
||||
className={classNames(small ? "w-8 h-8" : "w-10 h-10", "bg-gray-300 rounded-full flex-shrink-0")}>
|
||||
className={classNames(
|
||||
small ? "w-8 h-8" : "w-10 h-10",
|
||||
"bg-gray-300 rounded-full flex-shrink-0 relative"
|
||||
)}>
|
||||
<Avatar imageSrc={user?.avatar || ""} alt={user?.username || "Nameless User"} />
|
||||
{!user?.away && (
|
||||
<div className="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white rounded-full"></div>
|
||||
)}
|
||||
{user?.away && (
|
||||
<div className="absolute bottom-0 right-0 w-3 h-3 bg-yellow-500 border-2 border-white rounded-full"></div>
|
||||
)}
|
||||
</span>
|
||||
{!small && (
|
||||
<span className="flex items-center flex-grow truncate">
|
||||
|
@ -384,6 +396,26 @@ function UserDropdown({ small }: { small?: boolean }) {
|
|||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<a
|
||||
onClick={() => {
|
||||
mutation.mutate({ away: !user?.away });
|
||||
utils.invalidateQueries("viewer.me");
|
||||
}}
|
||||
className="flex px-4 py-2 text-sm cursor-pointer hover:bg-gray-100 hover:text-gray-900">
|
||||
<MoonIcon
|
||||
className={classNames(
|
||||
user?.away
|
||||
? "text-purple-500 group-hover:text-purple-700"
|
||||
: "text-gray-500 group-hover:text-gray-700",
|
||||
"mr-2 flex-shrink-0 h-5 w-5"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{user?.away ? t("set_as_free") : t("set_as_away")}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
||||
{user?.username && (
|
||||
<DropdownMenuItem>
|
||||
<a
|
||||
|
|
|
@ -26,7 +26,7 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
|
|||
return (
|
||||
<DropdownMenuPrimitive.Content
|
||||
{...props}
|
||||
className="z-10 mt-1 text-sm origin-top-right bg-white rounded-sm shadow-lg w-44 ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
className="z-10 w-48 mt-1 text-sm origin-top-right bg-white rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
ref={forwardedRef}>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.Content>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ArrowRightIcon } from "@heroicons/react/outline";
|
||||
import { MoonIcon } from "@heroicons/react/solid";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -49,23 +50,31 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
|
|||
<p className="text-neutral-500 dark:text-white">{user.bio}</p>
|
||||
</div>
|
||||
<div className="space-y-6" data-testid="event-types">
|
||||
{eventTypes.map((type) => (
|
||||
<div
|
||||
key={type.id}
|
||||
className="relative bg-white border rounded-sm group dark:bg-neutral-900 dark:border-0 dark:hover:border-neutral-600 hover:bg-gray-50 border-neutral-200 hover:border-brand">
|
||||
<ArrowRightIcon className="absolute w-4 h-4 text-black transition-opacity opacity-0 right-3 top-3 dark:text-white group-hover:opacity-100" />
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/${user.username}/${type.slug}`,
|
||||
query,
|
||||
}}>
|
||||
<a className="block px-6 py-4" data-testid="event-type-link">
|
||||
<h2 className="font-semibold text-neutral-900 dark:text-white">{type.title}</h2>
|
||||
<EventTypeDescription eventType={type} />
|
||||
</a>
|
||||
</Link>
|
||||
{user.away && (
|
||||
<div className="relative px-6 py-4 bg-white border rounded-sm group dark:bg-neutral-900 dark:border-0 border-neutral-200">
|
||||
<MoonIcon className="w-8 h-8 mb-4 text-neutral-800" />
|
||||
<h2 className="font-semibold text-neutral-900 dark:text-white">{t("user_away")}</h2>
|
||||
<p className="text-neutral-500 dark:text-white">{t("user_away_description")}</p>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
{!user.away &&
|
||||
eventTypes.map((type) => (
|
||||
<div
|
||||
key={type.id}
|
||||
className="relative bg-white border rounded-sm group dark:bg-neutral-900 dark:border-0 dark:hover:border-neutral-600 hover:bg-gray-50 border-neutral-200 hover:border-brand">
|
||||
<ArrowRightIcon className="absolute w-4 h-4 text-black transition-opacity opacity-0 right-3 top-3 dark:text-white group-hover:opacity-100" />
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/${user.username}/${type.slug}`,
|
||||
query,
|
||||
}}>
|
||||
<a className="block px-6 py-4" data-testid="event-type-link">
|
||||
<h2 className="font-semibold text-neutral-900 dark:text-white">{type.title}</h2>
|
||||
<EventTypeDescription eventType={type} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{eventTypes.length === 0 && (
|
||||
<div className="overflow-hidden rounded-sm shadow">
|
||||
|
@ -102,6 +111,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
avatar: true,
|
||||
theme: true,
|
||||
plan: true,
|
||||
away: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "away" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -118,6 +118,7 @@ model User {
|
|||
brandColor String @default("#292929")
|
||||
// the location where the events will end up
|
||||
destinationCalendar DestinationCalendar?
|
||||
away Boolean @default(false)
|
||||
metadata Json?
|
||||
|
||||
@@map(name: "users")
|
||||
|
|
|
@ -570,5 +570,9 @@
|
|||
"error_required_field": "This field is required.",
|
||||
"status": "Status",
|
||||
"team_view_user_availability": "View user availability",
|
||||
"team_view_user_availability_disabled": "User needs to accept invite to view availability"
|
||||
"team_view_user_availability_disabled": "User needs to accept invite to view availability",
|
||||
"set_as_away": "Set yourself as away",
|
||||
"set_as_free": "Disable away status",
|
||||
"user_away": "This user is currently away.",
|
||||
"user_away_description": "The person you are trying to book has set themselves to away, and therefore is not accepting new bookings."
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ async function getUserFromSession({
|
|||
twoFactorEnabled: true,
|
||||
brandColor: true,
|
||||
plan: true,
|
||||
away: true,
|
||||
credentials: {
|
||||
select: {
|
||||
id: true,
|
||||
|
|
|
@ -57,6 +57,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
twoFactorEnabled,
|
||||
brandColor,
|
||||
plan,
|
||||
away,
|
||||
} = ctx.user;
|
||||
const me = {
|
||||
id,
|
||||
|
@ -73,10 +74,26 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
twoFactorEnabled,
|
||||
brandColor,
|
||||
plan,
|
||||
away,
|
||||
};
|
||||
return me;
|
||||
},
|
||||
})
|
||||
.mutation("away", {
|
||||
input: z.object({
|
||||
away: z.boolean(),
|
||||
}),
|
||||
async resolve({ input, ctx }) {
|
||||
await ctx.prisma.user.update({
|
||||
where: {
|
||||
email: ctx.user.email,
|
||||
},
|
||||
data: {
|
||||
away: input.away,
|
||||
},
|
||||
});
|
||||
},
|
||||
})
|
||||
.query("eventTypes", {
|
||||
async resolve({ ctx }) {
|
||||
const { prisma } = ctx;
|
||||
|
|
Loading…
Reference in New Issue