Skip to content

Commit

Permalink
Merge pull request #5 from deepsingh132/development
Browse files Browse the repository at this point in the history
Adds an Error boundary and also implements the feature to handle the increment of podcast views
  • Loading branch information
deepsingh132 committed Jul 10, 2024
2 parents c17aa3f + 4f17541 commit 8739bdf
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 52 deletions.
12 changes: 9 additions & 3 deletions app/(root)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import MobileNav from "@/components/MobileNav";
import RightSidebar from "@/components/RightSidebar";
import Image from "next/image";
import PodcastPlayer from "@/components/PodcastPlayer";
import { ErrorBoundary } from "react-error-boundary";
import EmptyState from "@/components/EmptyState";

export default function RootLayout({
children,
Expand All @@ -25,9 +27,13 @@ export default function RootLayout({
/>
<MobileNav />
</div>
<div className="flex flex-col md:pb-14">
{children}
</div>
<ErrorBoundary fallback={
<div className="flex min-h-screen justify-center items-center">
<EmptyState title="Not Found" />
</div>
}>
<div className="flex flex-col md:pb-14">{children}</div>
</ErrorBoundary>
</div>
</section>

Expand Down
107 changes: 65 additions & 42 deletions app/(root)/podcasts/[podcastId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
'use client'
"use client";

import EmptyState from '@/components/EmptyState'
import LoaderSpinner from '@/components/LoaderSpinner'
import PodcastCard from '@/components/PodcastCard'
import PodcastDetailPlayer from '@/components/PodcastDetailPlayer'
import { api } from '@/convex/_generated/api'
import { Id } from '@/convex/_generated/dataModel'
import { useUser } from '@clerk/nextjs'
import { useQuery } from 'convex/react'
import Image from 'next/image'
import React from 'react'
import EmptyState from "@/components/EmptyState";
import LoaderSpinner from "@/components/LoaderSpinner";
import PodcastCard from "@/components/PodcastCard";
import PodcastDetailPlayer from "@/components/PodcastDetailPlayer";
import { api } from "@/convex/_generated/api";
import { Id } from "@/convex/_generated/dataModel";
import { useUser } from "@clerk/nextjs";
import { useQuery } from "convex/react";
import Image from "next/image";
import React, { useEffect, useState } from "react";

const PodcastDetails = ({ params: { podcastId } }: { params: { podcastId: Id<'podcasts'> } }) => {
const PodcastDetails = ({
params: { podcastId },
}: {
params: { podcastId: Id<"podcasts"> };
}) => {
const { user } = useUser();

const podcast = useQuery(api.podcasts.getPodcastById, { podcastId })
const podcast = useQuery(api.podcasts.getPodcastById, { podcastId });
const [isFetching, setIsFetching] = useState(true);

const similarPodcasts = useQuery(api.podcasts.getPodcastByVoiceType, { podcastId })
const similarPodcasts = useQuery(api.podcasts.getPodcastByVoiceType, {
podcastId,
});

const isOwner = user?.id === podcast?.authorId;

if(!similarPodcasts || !podcast) return <LoaderSpinner />
useEffect(() => {
if (podcast || podcast === null) {
setIsFetching(false);
}
}, [podcast]);

if (isFetching) return <LoaderSpinner />;

if (!isFetching && !podcast)
return (
<div className="flex min-h-screen justify-center items-center">
<EmptyState title="Podcast not found" />
</div>
);

return (
<section className="flex w-full flex-col">
<header className="mt-9 flex items-center justify-between">
<h1 className="text-20 font-bold text-white-1">
Currenty Playing
</h1>
<h1 className="text-20 font-bold text-white-1">Currenty Playing</h1>
<figure className="flex gap-3">
<Image
src="/icons/headphone.svg"
Expand All @@ -39,41 +57,47 @@ const PodcastDetails = ({ params: { podcastId } }: { params: { podcastId: Id<'po
</figure>
</header>



{/* @ts-ignore */}
{/* @ts-ignore */}
<PodcastDetailPlayer
isOwner={isOwner}
podcastId={podcast._id}
podcastId={podcast?._id!}
{...podcast}
/>

<p className="text-white-2 text-16 pb-8 pt-[45px] font-medium max-md:text-center">{podcast?.podcastDescription}</p>
<p className="text-white-2 text-16 pb-8 pt-[45px] font-medium max-md:text-center">
{podcast?.podcastDescription}
</p>

<div className="flex flex-col gap-8">
<div className='flex flex-col gap-4'>
<h1 className='text-18 font-bold text-white-1'>Transcription</h1>
<p className="text-16 font-medium text-white-2">{podcast?.voicePrompt}</p>
<div className="flex flex-col gap-4">
<h1 className="text-18 font-bold text-white-1">Transcription</h1>
<p className="text-16 font-medium text-white-2">
{podcast?.voicePrompt}
</p>
</div>
<div className='flex flex-col gap-4'>
<h1 className='text-18 font-bold text-white-1'>Thumbnail Prompt</h1>
<p className="text-16 font-medium text-white-2">{podcast?.imagePrompt}</p>
<div className="flex flex-col gap-4">
<h1 className="text-18 font-bold text-white-1">Thumbnail Prompt</h1>
<p className="text-16 font-medium text-white-2">
{podcast?.imagePrompt}
</p>
</div>
</div>
<section className="mt-8 flex flex-col gap-5">
<h1 className="text-20 font-bold text-white-1">Similar Podcasts</h1>

{similarPodcasts && similarPodcasts.length > 0 ? (
<div className="podcast_grid">
{similarPodcasts?.map(({ _id, podcastTitle, podcastDescription, imageUrl }) => (
<PodcastCard
key={_id}
imgUrl={imageUrl as string}
title={podcastTitle}
description={podcastDescription}
podcastId={_id}
/>
))}
{similarPodcasts?.map(
({ _id, podcastTitle, podcastDescription, imageUrl }) => (
<PodcastCard
key={_id}
imgUrl={imageUrl as string}
title={podcastTitle}
description={podcastDescription}
podcastId={_id}
/>
)
)}
</div>
) : (
<>
Expand All @@ -85,9 +109,8 @@ const PodcastDetails = ({ params: { podcastId } }: { params: { podcastId: Id<'po
</>
)}
</section>

</section>
)
}
);
};

export default PodcastDetails
export default PodcastDetails;
8 changes: 5 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import "./globals.css";
import ConvexClerkProvider from "../providers/ConvexClerkProvider";
import AudioProvider from "@/providers/AudioProvider";
import { Toaster } from "@/components/ui/toaster";
import IsFetchingProvider from "@/providers/IsFetchingProvider";

import IsFetchingProvider from "@/providers/IsFetchingProvider";
import { ErrorBoundary } from "react-error-boundary";
const manrope = Manrope({ subsets: ["latin"] });

export const metadata: Metadata = {
Expand All @@ -24,6 +24,7 @@ export default function RootLayout({
return (
<ConvexClerkProvider>
<html lang="en" suppressHydrationWarning>
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<IsFetchingProvider>
<AudioProvider>
<body className={`${manrope.className}`}>
Expand All @@ -32,7 +33,8 @@ export default function RootLayout({
</body>
{/* <Script async src="https://js.stripe.com/v3/pricing-table.js"></Script> */}
</AudioProvider>
</IsFetchingProvider>
</IsFetchingProvider>
</ErrorBoundary>
</html>
</ConvexClerkProvider>
);
Expand Down
11 changes: 10 additions & 1 deletion components/PodcastDetailPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { PodcastDetailPlayerProps } from "@/types";
import LoaderSpinner from "./LoaderSpinner";
import { Button } from "./ui/button";
import { useToast } from "./ui/use-toast";
import { useClerk } from "@clerk/nextjs";

const PodcastDetailPlayer = ({
audioUrl,
Expand All @@ -25,10 +26,12 @@ const PodcastDetailPlayer = ({
authorId,
}: PodcastDetailPlayerProps) => {
const router = useRouter();
const { setAudio } = useAudio();
const { audio, setAudio } = useAudio();
const { toast } = useToast();
const { user } = useClerk();
const [isDeleting, setIsDeleting] = useState(false);
const deletePodcast = useMutation(api.podcasts.deletePodcast);
const incrementViews = useMutation(api.podcasts.incrementPodcastViews);

const handleDelete = async () => {
try {
Expand All @@ -54,6 +57,12 @@ const PodcastDetailPlayer = ({
author,
podcastId,
});
// increment views if user is not the author and the current audio is not the same as the audio being played
if (!isOwner && audio?.audioUrl !== audioUrl) {
setTimeout(() => {
incrementViews({ podcastId: podcastId });
}, 10000);
};
};

if (!imageUrl || !authorImageUrl) return <LoaderSpinner />;
Expand Down
46 changes: 44 additions & 2 deletions convex/podcasts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ConvexError, v } from "convex/values";

import { MutationCtx, mutation, query } from "./_generated/server";
import { Id } from "./_generated/dataModel";
import { rateLimit } from "@/lib/rateLimits";

type User = {
_id: string;
Expand All @@ -14,6 +16,8 @@ type User = {
totalPodcasts: number;
};

const INCREMENTPODCASTVIEWS = "incrementPodcastViews";

// create podcast mutation
export const createPodcast = mutation({
args: {
Expand Down Expand Up @@ -151,10 +155,20 @@ export const getAllPodcasts = query({
// this query will get the podcast by the podcastId.
export const getPodcastById = query({
args: {
podcastId: v.id("podcasts"),
podcastId: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.get(args.podcastId);
try {
const podcast = await ctx.db.get(args.podcastId as Id<"podcasts">);

if (!podcast) {
throw new Error("Podcast not found");
}

return podcast;
} catch (error) {
throw new Error("Podcast not found");
}
},
});

Expand Down Expand Up @@ -187,6 +201,34 @@ export const getPodcastByAuthorId = query({
},
});

export const incrementPodcastViews = mutation({
args: {
podcastId: v.id("podcasts"),
},
handler: async (ctx, args) => {
// TODO: Scale this with a distributed counter
const podcast = await ctx.db.get(args.podcastId);

if (!podcast) {
throw new ConvexError("Podcast not found");
}

const newRateLimit = await rateLimit(ctx, {
name: INCREMENTPODCASTVIEWS,
key: args.podcastId,
throws: false,
});

if (!newRateLimit || !newRateLimit.ok) {
return;
}

return await ctx.db.patch(args.podcastId, {
views: podcast.views + 1,
});
},
});

// this query will get the podcast by the search query.
export const getPodcastBySearch = query({
args: {
Expand Down
4 changes: 3 additions & 1 deletion lib/rateLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const { checkRateLimit, rateLimit, resetRateLimit } = defineRateLimits({
generateAudioAction: { kind: "fixed window", rate: 3, period: MINUTE*2 },
generateThumbnailAction: { kind: "fixed window", rate: 3, period: MINUTE*2 },
createPodcast: { kind: "fixed window", rate: 3, period: MINUTE*2 },
uploadFile: { kind: "fixed window", rate: 3, period: MINUTE*2 },
uploadFile: { kind: "fixed window", rate: 3, period: MINUTE * 2 },
// only allow 1 view per podcast every 2 minutes
incrementPodcastViews: { kind: "fixed window", rate: 1, period: MINUTE*2 },

});
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"openai": "^4.47.2",
"react": "^18",
"react-dom": "^18",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.51.5",
"stripe": "^15.12.0",
"svix": "^1.24.0",
Expand Down

0 comments on commit 8739bdf

Please sign in to comment.