add CTAs to empty state view
This commit is contained in:
parent
3bc29c1341
commit
5036d41461
8 changed files with 127 additions and 89 deletions
23
package-lock.json
generated
23
package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"fix-webm-duration": "^1.0.5",
|
||||
"jotai": "^2.0.4",
|
||||
"micro": "^10.0.1",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^13.3.0",
|
||||
|
|
@ -5061,6 +5062,22 @@
|
|||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/jotai": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.4.tgz",
|
||||
"integrity": "sha512-XkR1Jtm4a2iKV/7fdB3rNHjJz8FkkDMVczqiAok7lt8W4F69l/ZQkPGWSEV+hJOJXRn27k6XYKOoKEQIwBuAMA==",
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=17.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
|
||||
|
|
@ -10912,6 +10929,12 @@
|
|||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz",
|
||||
"integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ=="
|
||||
},
|
||||
"jotai": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/jotai/-/jotai-2.0.4.tgz",
|
||||
"integrity": "sha512-XkR1Jtm4a2iKV/7fdB3rNHjJz8FkkDMVczqiAok7lt8W4F69l/ZQkPGWSEV+hJOJXRn27k6XYKOoKEQIwBuAMA==",
|
||||
"requires": {}
|
||||
},
|
||||
"js-sdsl": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"fix-webm-duration": "^1.0.5",
|
||||
"jotai": "^2.0.4",
|
||||
"micro": "^10.0.1",
|
||||
"micro-cors": "^0.1.1",
|
||||
"next": "^13.3.0",
|
||||
|
|
|
|||
3
src/atoms/recordVideoModalOpen.ts
Normal file
3
src/atoms/recordVideoModalOpen.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { atom } from "jotai";
|
||||
|
||||
export default atom<boolean>(false);
|
||||
3
src/atoms/uploadVideoModalOpen.ts
Normal file
3
src/atoms/uploadVideoModalOpen.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { atom } from "jotai";
|
||||
|
||||
export default atom<boolean>(false);
|
||||
|
|
@ -1,65 +1,58 @@
|
|||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Fragment, useState } from "react";
|
||||
import VideoRecordModal from "~/components/VideoRecordModal";
|
||||
import VideoUploadModal from "~/components/VideoUploadModal";
|
||||
import { Fragment } from "react";
|
||||
import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen";
|
||||
import { useAtom } from "jotai";
|
||||
import recordVideoModalOpen from "~/atoms/recordVideoModalOpen";
|
||||
|
||||
export default function NewVideoMenu() {
|
||||
const [recordOpen, setRecordOpen] = useState<boolean>(false);
|
||||
const [uploadOpen, setUploadOpen] = useState<boolean>(false);
|
||||
const [, setRecordOpen] = useAtom(uploadVideoModalOpen);
|
||||
const [, setUploadOpen] = useAtom(recordVideoModalOpen);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VideoRecordModal open={recordOpen} setOpen={setRecordOpen} />
|
||||
<VideoUploadModal open={uploadOpen} setOpen={setUploadOpen} />
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<Menu.Button>
|
||||
<span className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]">
|
||||
New video
|
||||
</span>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 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="px-1 py-1 ">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<div
|
||||
onClick={() => setRecordOpen(true)}
|
||||
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
||||
active ? "bg-gray-100" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="leading-2 text-sm leading-4">
|
||||
Record a video
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<div
|
||||
onClick={() => setUploadOpen(true)}
|
||||
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
||||
active ? "bg-gray-100" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="leading-2 text-sm leading-4">
|
||||
Upload a video
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</>
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<Menu.Button>
|
||||
<span className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]">
|
||||
New video
|
||||
</span>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="absolute right-0 z-10 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="px-1 py-1 ">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<div
|
||||
onClick={() => setRecordOpen(true)}
|
||||
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
||||
active ? "bg-gray-100" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="leading-2 text-sm leading-4">Record a video</p>
|
||||
</div>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<div
|
||||
onClick={() => setUploadOpen(true)}
|
||||
className={`mx-2 flex h-8 w-40 cursor-pointer flex-row content-center rounded-md p-2 ${
|
||||
active ? "bg-gray-100" : ""
|
||||
}`}
|
||||
>
|
||||
<p className="leading-2 text-sm leading-4">Upload a video</p>
|
||||
</div>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@ import { Fragment, useState } from "react";
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
const Recorder = dynamic(() => import("~/components/Recorder"), { ssr: false });
|
||||
import dynamic from "next/dynamic";
|
||||
import { useAtom } from "jotai";
|
||||
import recordVideoModalOpen from "~/atoms/recordVideoModalOpen";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export default function VideoRecordModal({ open, setOpen }: Props) {
|
||||
export default function VideoRecordModal() {
|
||||
const [open, setOpen] = useAtom(recordVideoModalOpen);
|
||||
const [step, setStep] = useState<"pre" | "in" | "post">("pre");
|
||||
|
||||
function closeModal() {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ import { Dialog, Transition } from "@headlessui/react";
|
|||
import { api } from "~/utils/api";
|
||||
import axios from "axios";
|
||||
import { useRouter } from "next/router";
|
||||
import { useAtom } from "jotai";
|
||||
import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export default function VideoUploadModal({ open, setOpen }: Props) {
|
||||
export default function VideoUploadModal() {
|
||||
const [open, setOpen] = useAtom(uploadVideoModalOpen);
|
||||
const router = useRouter();
|
||||
const [submitting, setSubmitting] = useState<boolean>(false);
|
||||
const [file, setFile] = useState<File>();
|
||||
|
|
|
|||
|
|
@ -7,13 +7,19 @@ import { useSession } from "next-auth/react";
|
|||
import { useRouter } from "next/router";
|
||||
import Image from "next/image";
|
||||
import { getTime } from "~/utils/getTime";
|
||||
import Checkout from "~/components/Checkout";
|
||||
import ProfileMenu from "~/components/ProfileMenu";
|
||||
import NewVideoMenu from "~/components/NewVideoMenu";
|
||||
import VideoRecordModal from "~/components/VideoRecordModal";
|
||||
import VideoUploadModal from "~/components/VideoUploadModal";
|
||||
import { useAtom } from "jotai";
|
||||
import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen";
|
||||
import recordVideoModalOpen from "~/atoms/recordVideoModalOpen";
|
||||
|
||||
const VideoList: NextPage = () => {
|
||||
const [, setRecordOpen] = useAtom(uploadVideoModalOpen);
|
||||
const [, setUploadOpen] = useAtom(recordVideoModalOpen);
|
||||
const router = useRouter();
|
||||
const { status, data: session } = useSession();
|
||||
const { status } = useSession();
|
||||
const { data: videos, isLoading } = api.video.getAll.useQuery();
|
||||
|
||||
if (status === "unauthenticated") {
|
||||
|
|
@ -31,11 +37,10 @@ const VideoList: NextPage = () => {
|
|||
<div className="flex min-h-[62px] w-full items-center justify-between border-b border-solid border-b-[#E7E9EB] bg-white px-6">
|
||||
<span>Snapify</span>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
{["active", "past_due"].includes(
|
||||
session?.user.stripeSubscriptionStatus ?? ""
|
||||
) ? (
|
||||
<NewVideoMenu />
|
||||
) : null}
|
||||
<VideoRecordModal />
|
||||
<VideoUploadModal />
|
||||
|
||||
<NewVideoMenu />
|
||||
{status === "authenticated" && (
|
||||
<div className="ml-4 flex items-center justify-center">
|
||||
<ProfileMenu />
|
||||
|
|
@ -44,12 +49,32 @@ const VideoList: NextPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex w-full grow items-start justify-center overflow-auto bg-[#fbfbfb] pt-14">
|
||||
{!isLoading &&
|
||||
status !== "loading" &&
|
||||
!["active", "past_due"].includes(
|
||||
session?.user.stripeSubscriptionStatus ?? ""
|
||||
) ? (
|
||||
<Checkout />
|
||||
{videos && videos?.length <= 0 ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-lg font-semibold text-zinc-700">
|
||||
No videos found
|
||||
</span>
|
||||
<span className="mt-1 text-base text-zinc-500">
|
||||
Videos you record will show up here. Already got videos?
|
||||
Upload them!
|
||||
</span>
|
||||
<div className="mt-4">
|
||||
<button
|
||||
onClick={() => setUploadOpen(true)}
|
||||
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Record a video
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setRecordOpen(true)}
|
||||
className="ml-4 inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Upload a video
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-start grid w-full max-w-[1300px] grid-cols-[repeat(auto-fill,250px)] flex-row flex-wrap items-center justify-center gap-14 px-4 pb-16">
|
||||
{videos &&
|
||||
|
|
@ -70,12 +95,6 @@ const VideoList: NextPage = () => {
|
|||
<VideoCardSkeleton />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{videos && videos?.length <= 0 ? (
|
||||
<div>
|
||||
<span>You do not have any recordings.</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue