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", "dayjs": "^1.11.7",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fix-webm-duration": "^1.0.5", "fix-webm-duration": "^1.0.5",
"jotai": "^2.0.4",
"micro": "^10.0.1", "micro": "^10.0.1",
"micro-cors": "^0.1.1", "micro-cors": "^0.1.1",
"next": "^13.3.0", "next": "^13.3.0",
@ -5061,6 +5062,22 @@
"url": "https://github.com/sponsors/panva" "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": { "node_modules/js-sdsl": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", "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", "resolved": "https://registry.npmjs.org/jose/-/jose-4.13.1.tgz",
"integrity": "sha512-MSJQC5vXco5Br38mzaQKiq9mwt7lwj2eXpgpRyQYNHYt2lq1PjkWa7DLXX0WVcQLE9HhMh3jPiufS7fhJf+CLQ==" "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": { "js-sdsl": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",

View file

@ -35,6 +35,7 @@
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fix-webm-duration": "^1.0.5", "fix-webm-duration": "^1.0.5",
"jotai": "^2.0.4",
"micro": "^10.0.1", "micro": "^10.0.1",
"micro-cors": "^0.1.1", "micro-cors": "^0.1.1",
"next": "^13.3.0", "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,16 +1,14 @@
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { Fragment, useState } from "react"; import { Fragment } from "react";
import VideoRecordModal from "~/components/VideoRecordModal"; import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen";
import VideoUploadModal from "~/components/VideoUploadModal"; import { useAtom } from "jotai";
import recordVideoModalOpen from "~/atoms/recordVideoModalOpen";
export default function NewVideoMenu() { export default function NewVideoMenu() {
const [recordOpen, setRecordOpen] = useState<boolean>(false); const [, setRecordOpen] = useAtom(uploadVideoModalOpen);
const [uploadOpen, setUploadOpen] = useState<boolean>(false); const [, setUploadOpen] = useAtom(recordVideoModalOpen);
return ( return (
<>
<VideoRecordModal open={recordOpen} setOpen={setRecordOpen} />
<VideoUploadModal open={uploadOpen} setOpen={setUploadOpen} />
<Menu as="div" className="relative inline-block text-left"> <Menu as="div" className="relative inline-block text-left">
<Menu.Button> <Menu.Button>
<span className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]"> <span className="cursor-pointer rounded border border-[#0000001a] px-2 py-2 text-sm text-[#292d34] hover:bg-[#fafbfc]">
@ -36,9 +34,7 @@ export default function NewVideoMenu() {
active ? "bg-gray-100" : "" active ? "bg-gray-100" : ""
}`} }`}
> >
<p className="leading-2 text-sm leading-4"> <p className="leading-2 text-sm leading-4">Record a video</p>
Record a video
</p>
</div> </div>
)} )}
</Menu.Item> </Menu.Item>
@ -50,9 +46,7 @@ export default function NewVideoMenu() {
active ? "bg-gray-100" : "" active ? "bg-gray-100" : ""
}`} }`}
> >
<p className="leading-2 text-sm leading-4"> <p className="leading-2 text-sm leading-4">Upload a video</p>
Upload a video
</p>
</div> </div>
)} )}
</Menu.Item> </Menu.Item>
@ -60,6 +54,5 @@ export default function NewVideoMenu() {
</Menu.Items> </Menu.Items>
</Transition> </Transition>
</Menu> </Menu>
</>
); );
} }

View file

@ -2,13 +2,11 @@ import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
const Recorder = dynamic(() => import("~/components/Recorder"), { ssr: false }); const Recorder = dynamic(() => import("~/components/Recorder"), { ssr: false });
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useAtom } from "jotai";
import recordVideoModalOpen from "~/atoms/recordVideoModalOpen";
interface Props { export default function VideoRecordModal() {
open: boolean; const [open, setOpen] = useAtom(recordVideoModalOpen);
setOpen: (value: boolean) => void;
}
export default function VideoRecordModal({ open, setOpen }: Props) {
const [step, setStep] = useState<"pre" | "in" | "post">("pre"); const [step, setStep] = useState<"pre" | "in" | "post">("pre");
function closeModal() { function closeModal() {

View file

@ -3,13 +3,11 @@ import { Dialog, Transition } from "@headlessui/react";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import axios from "axios"; import axios from "axios";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useAtom } from "jotai";
import uploadVideoModalOpen from "~/atoms/uploadVideoModalOpen";
interface Props { export default function VideoUploadModal() {
open: boolean; const [open, setOpen] = useAtom(uploadVideoModalOpen);
setOpen: (value: boolean) => void;
}
export default function VideoUploadModal({ open, setOpen }: Props) {
const router = useRouter(); const router = useRouter();
const [submitting, setSubmitting] = useState<boolean>(false); const [submitting, setSubmitting] = useState<boolean>(false);
const [file, setFile] = useState<File>(); const [file, setFile] = useState<File>();

View file

@ -7,13 +7,19 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Image from "next/image"; import Image from "next/image";
import { getTime } from "~/utils/getTime"; import { getTime } from "~/utils/getTime";
import Checkout from "~/components/Checkout";
import ProfileMenu from "~/components/ProfileMenu"; import ProfileMenu from "~/components/ProfileMenu";
import NewVideoMenu from "~/components/NewVideoMenu"; 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 VideoList: NextPage = () => {
const [, setRecordOpen] = useAtom(uploadVideoModalOpen);
const [, setUploadOpen] = useAtom(recordVideoModalOpen);
const router = useRouter(); const router = useRouter();
const { status, data: session } = useSession(); const { status } = useSession();
const { data: videos, isLoading } = api.video.getAll.useQuery(); const { data: videos, isLoading } = api.video.getAll.useQuery();
if (status === "unauthenticated") { 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"> <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> <span>Snapify</span>
<div className="flex flex-row items-center justify-center"> <div className="flex flex-row items-center justify-center">
{["active", "past_due"].includes( <VideoRecordModal />
session?.user.stripeSubscriptionStatus ?? "" <VideoUploadModal />
) ? (
<NewVideoMenu /> <NewVideoMenu />
) : null}
{status === "authenticated" && ( {status === "authenticated" && (
<div className="ml-4 flex items-center justify-center"> <div className="ml-4 flex items-center justify-center">
<ProfileMenu /> <ProfileMenu />
@ -44,12 +49,32 @@ const VideoList: NextPage = () => {
</div> </div>
</div> </div>
<div className="flex w-full grow items-start justify-center overflow-auto bg-[#fbfbfb] pt-14"> <div className="flex w-full grow items-start justify-center overflow-auto bg-[#fbfbfb] pt-14">
{!isLoading && {videos && videos?.length <= 0 ? (
status !== "loading" && <div className="flex items-center justify-center">
!["active", "past_due"].includes( <div className="flex flex-col">
session?.user.stripeSubscriptionStatus ?? "" <span className="text-lg font-semibold text-zinc-700">
) ? ( No videos found
<Checkout /> </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"> <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 && {videos &&
@ -70,12 +95,6 @@ const VideoList: NextPage = () => {
<VideoCardSkeleton /> <VideoCardSkeleton />
</> </>
) : null} ) : null}
{videos && videos?.length <= 0 ? (
<div>
<span>You do not have any recordings.</span>
</div>
) : null}
</div> </div>
)} )}
</div> </div>