add CTAs to empty state view

This commit is contained in:
MarconLP 2023-04-20 16:32:45 +02:00
parent 3bc29c1341
commit 5036d41461
No known key found for this signature in database
GPG key ID: A08A9C8B623F5EA5
8 changed files with 127 additions and 89 deletions

23
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -0,0 +1,3 @@
import { atom } from "jotai";
export default atom<boolean>(false);

View file

@ -0,0 +1,3 @@
import { atom } from "jotai";
export default atom<boolean>(false);

View file

@ -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>
);
}

View file

@ -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() {

View file

@ -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>();

View 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>