add shareModal and the ability to toggle share state
This commit is contained in:
parent
fcb3ecbb14
commit
c089fb7d47
5 changed files with 122 additions and 57 deletions
|
|
@ -32,6 +32,12 @@ const config = {
|
||||||
port: '',
|
port: '',
|
||||||
pathname: '/**',
|
pathname: '/**',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: '*.imgur.com',
|
||||||
|
port: '',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: '*.githubusercontent.com',
|
hostname: '*.githubusercontent.com',
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,13 @@ model Video {
|
||||||
title String
|
title String
|
||||||
video_url String
|
video_url String
|
||||||
userId String
|
userId String
|
||||||
|
sharing Boolean @default(false)
|
||||||
|
deleteAfterExpiry Boolean @default(false)
|
||||||
|
shareExpiryAt DateTime?
|
||||||
|
linkShareSeo Boolean @default(false)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Necessary for Next auth
|
// Necessary for Next auth
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,36 @@
|
||||||
import { Dialog, Switch, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
import { ModernSwitch } from "~/components/ModernSwitch";
|
import { ModernSwitch } from "~/components/ModernSwitch";
|
||||||
|
import { api, type RouterOutputs } from "~/utils/api";
|
||||||
|
|
||||||
export function ShareModal() {
|
interface Props {
|
||||||
|
video: RouterOutputs["video"]["get"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShareModal({ video }: Props) {
|
||||||
|
const utils = api.useContext();
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const [sharing, setSharing] = useState<boolean>(false);
|
const setSharingMutation = api.video.setSharing.useMutation({
|
||||||
const [es, sses] = useState<boolean>(false);
|
onMutate: async ({ videoId, sharing }) => {
|
||||||
const [as, ssgs] = useState<boolean>(false);
|
await utils.video.get.cancel();
|
||||||
|
const previousValue = utils.video.get.getData({ videoId });
|
||||||
|
if (previousValue) {
|
||||||
|
utils.video.get.setData({ videoId }, { ...previousValue, sharing });
|
||||||
|
}
|
||||||
|
return { previousValue };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const [s, ss] = useState<boolean>(false);
|
const [linkCopied, setLinkCopied] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
void navigator.clipboard.writeText(window.location.href);
|
||||||
|
setLinkCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setLinkCopied(false);
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -58,34 +79,52 @@ export function ShareModal() {
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
Share link with anyone
|
Share link with anyone
|
||||||
</span>
|
</span>
|
||||||
<ModernSwitch enabled={s} toggle={ss} />
|
<ModernSwitch
|
||||||
|
enabled={video.sharing}
|
||||||
|
toggle={() =>
|
||||||
|
setSharingMutation.mutate({
|
||||||
|
videoId: video.id,
|
||||||
|
sharing: !video.sharing,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button className="my-2 h-8 w-full rounded-md bg-[#4169e1] text-sm font-medium text-white hover:bg-[#224fd7]">
|
{video.sharing ? (
|
||||||
Copy public link
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="my-2 h-8 w-full rounded-md bg-[#4169e1] text-sm font-medium text-white hover:bg-[#224fd7]"
|
||||||
|
>
|
||||||
|
{linkCopied ? "Copied!" : "Copy public link"}
|
||||||
</button>
|
</button>
|
||||||
<div className="w-full border border-solid border-[#e9ebf0] bg-[#fafbfc] px-[15px] py-3 text-xs">
|
<div className="w-full border border-solid border-[#e9ebf0] bg-[#fafbfc] px-[15px] py-3 text-xs">
|
||||||
<div className="flex h-6 items-center justify-between">
|
{/*<div className="flex h-6 items-center justify-between">*/}
|
||||||
<span>Expire link</span>
|
{/* <span>Expire link</span>*/}
|
||||||
|
|
||||||
<button className="h-6 rounded border border-solid border-[#d5d9df] bg-white px-[7px]">
|
{/* <button className="h-6 rounded border border-solid border-[#d5d9df] bg-white px-[7px] font-medium">*/}
|
||||||
Never expire
|
{/* Never expire*/}
|
||||||
</button>
|
{/* </button>*/}
|
||||||
</div>
|
{/*</div>*/}
|
||||||
<div className="mt-3 flex h-6 items-center justify-between">
|
{/*<div className="mt-3 flex h-6 items-center justify-between">*/}
|
||||||
<span>Delete video when expired</span>
|
{/* <span>Delete video when expired</span>*/}
|
||||||
<ModernSwitch enabled={s} toggle={ss} />
|
{/* <ModernSwitch enabled={s} toggle={ss} />*/}
|
||||||
</div>
|
{/*</div>*/}
|
||||||
<div className="mt-3 flex h-6 items-center justify-between">
|
<div className="mt-3 flex h-6 items-center justify-between">
|
||||||
<span>Share link with search engines</span>
|
<span>Share link with search engines</span>
|
||||||
<ModernSwitch enabled={s} toggle={ss} />
|
<ModernSwitch
|
||||||
</div>
|
enabled={video.linkShareSeo}
|
||||||
<div className="mt-3 flex h-6 items-center justify-between">
|
toggle={() => console.log("test")}
|
||||||
<span>Embed code</span>
|
/>
|
||||||
<button className="h-6 rounded border border-solid border-[#d5d9df] bg-white px-[7px]">
|
|
||||||
Copy code
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{/*<div className="mt-3 flex h-6 items-center justify-between">*/}
|
||||||
|
{/* <span>Embed code</span>*/}
|
||||||
|
{/* <button className="h-6 rounded border border-solid border-[#d5d9df] bg-white px-[7px] font-medium">*/}
|
||||||
|
{/* Copy code*/}
|
||||||
|
{/* </button>*/}
|
||||||
|
{/*</div>*/}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ const VideoList: NextPage = () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isLoading && !video?.success) {
|
if (!isLoading && !video) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen flex-col items-center justify-center">
|
<div className="flex h-screen w-screen flex-col items-center justify-center">
|
||||||
<span className="max-w-[80%] text-center text-2xl font-medium">
|
<span className="max-w-[80%] text-center text-2xl font-medium">
|
||||||
|
|
@ -50,29 +50,29 @@ const VideoList: NextPage = () => {
|
||||||
Personal Library
|
Personal Library
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<ShareModal />
|
{video ? <ShareModal video={video} /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full w-full grow flex-col items-center justify-start overflow-auto bg-[#fbfbfb]">
|
<div className="flex h-full w-full grow flex-col items-center justify-start overflow-auto bg-[#fbfbfb]">
|
||||||
<div className="flex aspect-video max-h-[627px] w-full justify-center bg-black 2xl:max-h-[1160px]">
|
<div className="flex aspect-video max-h-[627px] w-full justify-center bg-black 2xl:max-h-[1160px]">
|
||||||
{video?.video?.video_url && (
|
{video?.video_url && (
|
||||||
<ReactPlayer
|
<ReactPlayer
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
controls={true}
|
controls={true}
|
||||||
url={video.video?.video_url}
|
url={video?.video_url}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-10 mt-4 w-full max-w-[1800px] pl-[24px]">
|
<div className="mb-10 mt-4 w-full max-w-[1800px] pl-[24px]">
|
||||||
<div>
|
<div>
|
||||||
{video?.video?.title ? (
|
{video?.title ? (
|
||||||
<div className="mb-4 flex flex-col">
|
<div className="mb-4 flex flex-col">
|
||||||
<span className="text-[18px] text-lg font-medium">
|
<span className="text-[18px] text-lg font-medium">
|
||||||
{video?.video?.title}
|
{video.title}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[18px] text-sm text-gray-800">
|
<span className="text-[18px] text-sm text-gray-800">
|
||||||
{getTime(video?.video?.createdAt)}
|
{getTime(video.createdAt)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -89,13 +89,14 @@ const VideoList: NextPage = () => {
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={40}
|
height={40}
|
||||||
src={video?.video?.user?.image ?? ""}
|
src={
|
||||||
|
video.user.image ??
|
||||||
|
"https://i.stack.imgur.com/dr5qp.jpg"
|
||||||
|
}
|
||||||
alt="profile photo"
|
alt="profile photo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="ml-3 font-medium">
|
<span className="ml-3 font-medium">{video.user.name}</span>
|
||||||
{video?.video?.user?.name}
|
|
||||||
</span>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||||
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
|
||||||
import { env } from "~/env.mjs";
|
import { env } from "~/env.mjs";
|
||||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
export const videoRouter = createTRPCRouter({
|
export const videoRouter = createTRPCRouter({
|
||||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||||
|
|
@ -29,9 +30,7 @@ export const videoRouter = createTRPCRouter({
|
||||||
});
|
});
|
||||||
|
|
||||||
if (video?.userId !== ctx.session.user.id) {
|
if (video?.userId !== ctx.session.user.id) {
|
||||||
return {
|
throw new TRPCError({ code: "FORBIDDEN" });
|
||||||
success: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getObjectCommand = new GetObjectCommand({
|
const getObjectCommand = new GetObjectCommand({
|
||||||
|
|
@ -43,10 +42,7 @@ export const videoRouter = createTRPCRouter({
|
||||||
|
|
||||||
video.video_url = signedUrl;
|
video.video_url = signedUrl;
|
||||||
|
|
||||||
return {
|
return video;
|
||||||
success: true,
|
|
||||||
video,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
getUploadUrl: protectedProcedure
|
getUploadUrl: protectedProcedure
|
||||||
.input(z.object({ key: z.string() }))
|
.input(z.object({ key: z.string() }))
|
||||||
|
|
@ -77,4 +73,21 @@ export const videoRouter = createTRPCRouter({
|
||||||
signedUrl,
|
signedUrl,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
setSharing: protectedProcedure
|
||||||
|
.input(z.object({ videoId: z.string(), sharing: z.boolean() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const updateVideo = await ctx.prisma.video.update({
|
||||||
|
where: {
|
||||||
|
id: input.videoId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
sharing: input.sharing,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
updateVideo,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue