add ability to rename video
This commit is contained in:
parent
b2270dc6c4
commit
f5374242ae
3 changed files with 165 additions and 38 deletions
|
|
@ -37,6 +37,7 @@ export default function ExpireDateSelectMenu({
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (value: string) => {
|
const handleChange = (value: string) => {
|
||||||
let timestamp: null | Date = new Date();
|
let timestamp: null | Date = new Date();
|
||||||
switch (value) {
|
switch (value) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { type RouterOutputs } from "~/utils/api";
|
import { api, type RouterOutputs } from "~/utils/api";
|
||||||
import { Menu, Transition } from "@headlessui/react";
|
import { Dialog, Menu, Transition } from "@headlessui/react";
|
||||||
import { Fragment } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import {
|
import {
|
||||||
DotsHorizontalIcon,
|
DotsHorizontalIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
|
|
@ -13,10 +13,19 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function VideoMoreMenu({ video }: Props) {
|
export default function VideoMoreMenu({ video }: Props) {
|
||||||
|
const [title, setTitle] = useState(video.title);
|
||||||
|
const [renameMenuOpen, setRenameMenuOpen] = useState<boolean>(false);
|
||||||
|
const utils = api.useContext();
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
name: "Rename",
|
name: "Rename",
|
||||||
icon: <Pencil1Icon />,
|
icon: <Pencil1Icon />,
|
||||||
|
props: {
|
||||||
|
onClick: () => {
|
||||||
|
setRenameMenuOpen(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Download",
|
name: "Download",
|
||||||
|
|
@ -42,45 +51,135 @@ export default function VideoMoreMenu({ video }: Props) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const renameVideoMutation = api.video.renameVideo.useMutation({
|
||||||
|
onMutate: async ({ videoId, title }) => {
|
||||||
|
await utils.video.get.cancel();
|
||||||
|
const previousValue = utils.video.get.getData({ videoId });
|
||||||
|
if (previousValue) {
|
||||||
|
utils.video.get.setData({ videoId }, { ...previousValue, title });
|
||||||
|
}
|
||||||
|
return { previousValue };
|
||||||
|
},
|
||||||
|
onError: (err, { videoId }, context) => {
|
||||||
|
if (context?.previousValue) {
|
||||||
|
utils.video.get.setData({ videoId }, context.previousValue);
|
||||||
|
}
|
||||||
|
console.error(err.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative mr-4 inline-block text-left">
|
<>
|
||||||
<div>
|
{/* More options menu */}
|
||||||
<Menu.Button className="inline-flex h-full w-full justify-center rounded-full px-4 py-2 text-sm font-medium text-black text-white hover:bg-[#fafbfc] focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
<Menu as="div" className="relative mr-4 inline-block text-left">
|
||||||
<DotsHorizontalIcon />
|
<div>
|
||||||
</Menu.Button>
|
<Menu.Button className="inline-flex h-full w-full justify-center rounded-full px-4 py-2 text-sm font-medium text-black text-white hover:bg-[#fafbfc] focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
|
||||||
</div>
|
<DotsHorizontalIcon />
|
||||||
<Transition
|
</Menu.Button>
|
||||||
as={Fragment}
|
</div>
|
||||||
enter="transition ease-out duration-100"
|
<Transition
|
||||||
enterFrom="transform opacity-0 scale-95"
|
as={Fragment}
|
||||||
enterTo="transform opacity-100 scale-100"
|
enter="transition ease-out duration-100"
|
||||||
leave="transition ease-in duration-75"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
enterTo="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leave="transition ease-in duration-75"
|
||||||
>
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
<Menu.Items className="absolute right-0 z-20 mt-2 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
leaveTo="transform opacity-0 scale-95"
|
||||||
<div className="px-1 py-1 ">
|
>
|
||||||
{items.map((item) => (
|
<Menu.Items className="absolute right-0 z-20 mt-2 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||||
<div className="h-8" key={item.name} {...item.props}>
|
<div className="px-1 py-1 ">
|
||||||
<Menu.Item>
|
{items.map((item) => (
|
||||||
{({ active }) => (
|
<div className="h-8" key={item.name} {...item.props}>
|
||||||
<div
|
<Menu.Item>
|
||||||
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
{({ active }) => (
|
||||||
active ? "bg-gray-100" : ""
|
<div
|
||||||
}`}
|
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
||||||
>
|
active ? "bg-gray-100" : ""
|
||||||
<div className="mr-2 flex w-4 content-center justify-center">
|
}`}
|
||||||
{item.icon}
|
>
|
||||||
|
<div className="mr-2 flex w-4 content-center justify-center">
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
<p className="leading-2 text-sm leading-4">
|
||||||
|
{item.name}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="leading-2 text-sm leading-4">{item.name}</p>
|
)}
|
||||||
|
</Menu.Item>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Menu.Items>
|
||||||
|
</Transition>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
{/* Rename dialog */}
|
||||||
|
<Transition appear show={renameMenuOpen} as={Fragment}>
|
||||||
|
<Dialog
|
||||||
|
as="div"
|
||||||
|
className="relative z-10"
|
||||||
|
onClose={() => setRenameMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||||
|
</Transition.Child>
|
||||||
|
|
||||||
|
<div className="fixed inset-0 overflow-y-auto">
|
||||||
|
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||||
|
<Transition.Child
|
||||||
|
as={Fragment}
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enterFrom="opacity-0 scale-95"
|
||||||
|
enterTo="opacity-100 scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leaveFrom="opacity-100 scale-100"
|
||||||
|
leaveTo="opacity-0 scale-95"
|
||||||
|
>
|
||||||
|
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all">
|
||||||
|
<div className="sm:col-span-4">
|
||||||
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
className="block text-sm font-medium leading-6 text-gray-900"
|
||||||
|
>
|
||||||
|
Video title
|
||||||
|
</label>
|
||||||
|
<div className="mt-2">
|
||||||
|
<input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.currentTarget.value)}
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
className="block w-full rounded-md border-0 px-2 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</Menu.Item>
|
|
||||||
</div>
|
<button
|
||||||
))}
|
type="submit"
|
||||||
|
onClick={() => {
|
||||||
|
renameVideoMutation.mutate({ videoId: video.id, title });
|
||||||
|
setRenameMenuOpen(false);
|
||||||
|
}}
|
||||||
|
className="mt-2 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</Transition.Child>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Menu.Items>
|
</Dialog>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Menu>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,33 @@ export const videoRouter = createTRPCRouter({
|
||||||
throw new TRPCError({ code: "FORBIDDEN" });
|
throw new TRPCError({ code: "FORBIDDEN" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
updateVideo,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
renameVideo: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
videoId: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const updateVideo = await ctx.prisma.video.updateMany({
|
||||||
|
where: {
|
||||||
|
id: input.videoId,
|
||||||
|
userId: ctx.session.user.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: input.title,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateVideo.count === 0) {
|
||||||
|
throw new TRPCError({ code: "FORBIDDEN" });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
updateVideo,
|
updateVideo,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue