diff --git a/.vscode/launch.json b/.vscode/launch.json index c71d7268..9e83995d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,29 +1,29 @@ { - "version": "0.2.0", - "runtimeArgs": ["--preserve-symlinks"], - "configurations": [ - { - "name": "Next.js: debug server-side", - "type": "node-terminal", - "request": "launch", - "command": "npm run dev" - }, - { - "name": "Next.js: debug client-side", - "type": "chrome", - "request": "launch", - "url": "http://localhost:3000" - }, - { - "name": "Next.js: debug full stack", - "type": "node-terminal", - "request": "launch", - "command": "npm run dev", - "serverReadyAction": { - "pattern": "- Local:.+(https?://.+)", - "uriFormat": "%s", - "action": "debugWithChrome" - } - } - ] + "version": "0.2.0", + "runtimeArgs": ["--preserve-symlinks"], + "configurations": [ + { + "name": "Next.js: debug server-side", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev" + }, + { + "name": "Next.js: debug client-side", + "type": "chrome", + "request": "launch", + "url": "http://localhost:3000" + }, + { + "name": "Next.js: debug full stack", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "serverReadyAction": { + "pattern": "- Local:.+(https?://.+)", + "uriFormat": "%s", + "action": "debugWithChrome" + } + } + ] } diff --git a/app/(main)/(auth)/login/github.tsx b/app/(main)/(auth)/login/github.tsx index a06cc6ab..689809ef 100644 --- a/app/(main)/(auth)/login/github.tsx +++ b/app/(main)/(auth)/login/github.tsx @@ -13,46 +13,46 @@ import { FaGithub } from "react-icons/fa"; import { useRouter, useSearchParams } from "next/navigation"; export default function GitHubAuth() { - const router = useRouter(); - const searchParams = useSearchParams(); - const redirect = searchParams.get("redirectTo"); - const { toast } = useToast(); + const router = useRouter(); + const searchParams = useSearchParams(); + const redirect = searchParams.get("redirectTo"); + const { toast } = useToast(); - const login = async () => { - try { - await ccdSignInWithPopUp(provider); - redirect - ? router.replace( - redirect === "/pro" ? `${redirect}?showSubscribe=true` : redirect - ) - : router.replace("/dashboard"); - } catch (err: any) { - if (err instanceof FirebaseError) { - if (err.code === "auth/account-exists-with-different-credential") { - toast({ - variant: "destructive", - description: - "Account Exists with Different Login Method. Please first login and then link within your Account page.", - }); - } else { - toast({ - variant: "destructive", - description: err.message, - }); - } - } else { - toast({ - variant: "destructive", - description: JSON.stringify(err), - }); - console.error(err); - } - } - }; + const login = async () => { + try { + await ccdSignInWithPopUp(provider); + redirect + ? router.replace( + redirect === "/pro" ? `${redirect}?showSubscribe=true` : redirect, + ) + : router.replace("/dashboard"); + } catch (err: any) { + if (err instanceof FirebaseError) { + if (err.code === "auth/account-exists-with-different-credential") { + toast({ + variant: "destructive", + description: + "Account Exists with Different Login Method. Please first login and then link within your Account page.", + }); + } else { + toast({ + variant: "destructive", + description: err.message, + }); + } + } else { + toast({ + variant: "destructive", + description: JSON.stringify(err), + }); + console.error(err); + } + } + }; - return ( - - ); + return ( + + ); } diff --git a/app/(main)/(auth)/login/google.tsx b/app/(main)/(auth)/login/google.tsx index f81c1fc5..0cdcd0c9 100644 --- a/app/(main)/(auth)/login/google.tsx +++ b/app/(main)/(auth)/login/google.tsx @@ -13,46 +13,46 @@ import { FaGoogle } from "react-icons/fa"; import { useRouter, useSearchParams } from "next/navigation"; export default function GoogleAuth() { - const router = useRouter(); - const searchParams = useSearchParams(); - const redirect = searchParams.get("redirectTo"); - const { toast } = useToast(); + const router = useRouter(); + const searchParams = useSearchParams(); + const redirect = searchParams.get("redirectTo"); + const { toast } = useToast(); - const login = async () => { - try { - await ccdSignInWithPopUp(provider); - redirect - ? router.replace( - redirect === "/pro" ? `${redirect}?showSubscribe=true` : redirect - ) - : router.replace("/dashboard"); - } catch (err: any) { - if (err instanceof FirebaseError) { - if (err.code === "auth/account-exists-with-different-credential") { - toast({ - variant: "destructive", - description: - "Account Exists with Different Login Method. Please first login and then link within your Account page.", - }); - } else { - toast({ - variant: "destructive", - description: err.message, - }); - } - } else { - toast({ - variant: "destructive", - description: JSON.stringify(err), - }); - console.error(err); - } - } - }; + const login = async () => { + try { + await ccdSignInWithPopUp(provider); + redirect + ? router.replace( + redirect === "/pro" ? `${redirect}?showSubscribe=true` : redirect, + ) + : router.replace("/dashboard"); + } catch (err: any) { + if (err instanceof FirebaseError) { + if (err.code === "auth/account-exists-with-different-credential") { + toast({ + variant: "destructive", + description: + "Account Exists with Different Login Method. Please first login and then link within your Account page.", + }); + } else { + toast({ + variant: "destructive", + description: err.message, + }); + } + } else { + toast({ + variant: "destructive", + description: JSON.stringify(err), + }); + console.error(err); + } + } + }; - return ( - - ); + return ( + + ); } diff --git a/app/(main)/(auth)/login/page.tsx b/app/(main)/(auth)/login/page.tsx index 1ff3bd7e..8949fde0 100644 --- a/app/(main)/(auth)/login/page.tsx +++ b/app/(main)/(auth)/login/page.tsx @@ -1,33 +1,33 @@ import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import GoogleAuth from "./google"; import GitHubAuth from "./github"; import { Suspense } from "react"; export default function LoginForm() { - return ( -
-
- - - Login - - Enter your email below to login to your account. - - - - - - - - - -
-
- ); + return ( +
+
+ + + Login + + Enter your email below to login to your account. + + + + + + + + + +
+
+ ); } diff --git a/app/(main)/(author)/author/[slug]/page.tsx b/app/(main)/(author)/author/[slug]/page.tsx index 654f0fe7..78574899 100644 --- a/app/(main)/(author)/author/[slug]/page.tsx +++ b/app/(main)/(author)/author/[slug]/page.tsx @@ -5,10 +5,10 @@ import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; import type { - AuthorQueryResult, - AuthorQueryWithRelatedResult, -} from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; + AuthorQueryResult, + AuthorQueryWithRelatedResult, +} from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { authorQuery, authorQueryWithRelated } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import CoverMedia from "@/components/cover-media"; @@ -17,80 +17,86 @@ import { BreadcrumbLinks } from "@/components/breadrumb-links"; import UserSocials from "@/components/user-socials"; import UserRelated from "@/components/user-related"; -type Props = { - params: { slug: string }; -}; +type Params = Promise<{ slug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const author = await sanityFetch({ - query: authorQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(author?.coverImage); + const { slug } = await params; - return { - title: author?.title, - description: author?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + const author = ( + await sanityFetch({ + query: authorQuery, + params: { slug }, + stega: false, + }) + ).data as AuthorQueryResult; + + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(author?.coverImage); + + return { + title: author?.title, + description: author?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function AuthorPage({ params }: Props) { - const [author] = await Promise.all([ - sanityFetch({ - query: authorQueryWithRelated, - params, - }), - ]); +export default async function AuthorPage({ params }: { params: Params }) { + const { slug } = await params; + + const [authorFetch] = await Promise.all([ + sanityFetch({ + query: authorQueryWithRelated, + params: { slug }, + }), + ]); + + const author = authorFetch.data as AuthorQueryWithRelatedResult; - if (!author?._id) { - return notFound(); - } + if (!author?._id) { + return notFound(); + } - return ( -
- -
-
-
- -
-
-

- {author.title} -

- {author?.socials && ( -
- -
- )} -
-
-
- {author.content?.length && ( - - )} -
-
- -
- ); + return ( +
+ +
+
+
+ +
+
+

+ {author.title} +

+ {author?.socials && ( +
+ +
+ )} +
+
+
+ {author.content?.length && ( + + )} +
+
+ +
+ ); } diff --git a/app/(main)/(author)/authors/page.tsx b/app/(main)/(author)/authors/page.tsx index 654233ed..37326b6f 100644 --- a/app/(main)/(author)/authors/page.tsx +++ b/app/(main)/(author)/authors/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/authors/page/1"); + redirect("/authors/page/1"); } diff --git a/app/(main)/(author)/authors/page/[num]/page.tsx b/app/(main)/(author)/authors/page/[num]/page.tsx index 0af30a2b..c0f84b99 100644 --- a/app/(main)/(author)/authors/page/[num]/page.tsx +++ b/app/(main)/(author)/authors/page/[num]/page.tsx @@ -1,40 +1,39 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "author", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const { num } = await params; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const count = ( + await sanityFetch({ + query: docCount, + params: { + type: "author", + }, + }) + ).data as DocCountResult; - return ( -
- - -
- ); + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; + + return ( +
+ + +
+ ); } diff --git a/app/(main)/(author)/authors/page/page.tsx b/app/(main)/(author)/authors/page/page.tsx index 654233ed..37326b6f 100644 --- a/app/(main)/(author)/authors/page/page.tsx +++ b/app/(main)/(author)/authors/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/authors/page/1"); + redirect("/authors/page/1"); } diff --git a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-client-only.tsx b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-client-only.tsx index 896ac770..59c1c138 100644 --- a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-client-only.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-client-only.tsx @@ -1,30 +1,30 @@ "use client"; import type { - LessonQueryResult, - LessonsInCourseQueryResult, -} from "@/sanity.types"; + LessonQueryResult, + LessonsInCourseQueryResult, +} from "@/sanity/types"; import { useEffect, useState } from "react"; import LessonPanel from "./lesson-panel"; export default function LessonPanelClientOnly({ - lesson, - course, + lesson, + course, }: { - lesson: NonNullable; - course: NonNullable; + lesson: NonNullable; + course: NonNullable; }) { - const [isClient, setIsClient] = useState(false); + const [isClient, setIsClient] = useState(false); - useEffect(() => { - setIsClient(true); - }, []); + useEffect(() => { + setIsClient(true); + }, []); - //TODO: Make this match better? - if (!isClient) return
Loading Lesson...
; + //TODO: Make this match better? + if (!isClient) return
Loading Lesson...
; - return ( - <> - - - ); + return ( + <> + + + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-complete.tsx b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-complete.tsx index 3cf4e55a..f721e477 100644 --- a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-complete.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-complete.tsx @@ -1,53 +1,53 @@ "use client"; import { useCompletedLesson, useFirestoreUser } from "@/lib/firebase.hooks"; import { Checkbox } from "@/components/ui/checkbox"; -import { LessonsInCourseQueryResult } from "@/sanity.types"; +import type { LessonsInCourseQueryResult } from "@/sanity/types"; import { useToast } from "@/components/ui/use-toast"; -import { BaseCompletedLesson } from "@/lib/types"; +import type { BaseCompletedLesson } from "@/lib/types"; export default function LessonComplete({ - lesson, - course, + lesson, + course, }: { - lesson: BaseCompletedLesson; - course: NonNullable; + lesson: BaseCompletedLesson; + course: NonNullable; }) { - const { currentUser } = useFirestoreUser(); - const { completeLesson, addComplete, removeComplete } = useCompletedLesson({ - lesson, - course, - }); - const { toast } = useToast(); + const { currentUser } = useFirestoreUser(); + const { completeLesson, addComplete, removeComplete } = useCompletedLesson({ + lesson, + course, + }); + const { toast } = useToast(); - const makeComplete = async (isChecked: boolean | "indeterminate") => { - if (!currentUser?.uid) { - toast({ - variant: "destructive", - description: "You must be logged in to complete a lesson.", - }); - return; - } - if (isChecked) { - await addComplete(); - toast({ - description: "What a rockstar! 🎉", - }); - } else { - await removeComplete(); - } - }; - return ( - <> - {currentUser?.uid ? ( -
- -
- ) : ( - <> - )} - - ); + const makeComplete = async (isChecked: boolean | "indeterminate") => { + if (!currentUser?.uid) { + toast({ + variant: "destructive", + description: "You must be logged in to complete a lesson.", + }); + return; + } + if (isChecked) { + await addComplete(); + toast({ + description: "What a rockstar! 🎉", + }); + } else { + await removeComplete(); + } + }; + return ( + <> + {currentUser?.uid ? ( +
+ +
+ ) : ( + <> + )} + + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-panel.tsx b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-panel.tsx index 44bd6856..980955ea 100644 --- a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-panel.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/lesson-panel.tsx @@ -1,162 +1,162 @@ "use client"; import { - ResizablePanelGroup, - ResizablePanel, - ResizableHandle, + ResizablePanelGroup, + ResizablePanel, + ResizableHandle, } from "@/components/ui/resizable"; import Link from "next/link"; import type { - LessonQueryResult, - LessonsInCourseQueryResult, -} from "@/sanity.types"; + LessonQueryResult, + LessonsInCourseQueryResult, +} from "@/sanity/types"; import BadgePro from "@/components/badge-pro"; import NavLesson from "./nav-lesson"; import CoverMedia from "@/components/cover-media"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { - FaCircleArrowLeft, - FaCircleArrowRight, - FaHouse, + FaCircleArrowLeft, + FaCircleArrowRight, + FaHouse, } from "react-icons/fa6"; import LessonComplete from "./lesson-complete"; import { useLocalStorage } from "@uidotdev/usehooks"; import Bookmark from "@/components/bookmark"; export default function LessonPanel({ - lesson, - course, + lesson, + course, }: { - lesson: NonNullable; - course: NonNullable; + lesson: NonNullable; + course: NonNullable; }) { - const [defaultLayout, saveDefaultLayout] = useLocalStorage( - "codingcatdev:lesson:layout", - [25, 75] - ); + const [defaultLayout, saveDefaultLayout] = useLocalStorage( + "codingcatdev:lesson:layout", + [25, 75], + ); - const onLayout = (sizes: number[]) => { - saveDefaultLayout(sizes); - }; + const onLayout = (sizes: number[]) => { + saveDefaultLayout(sizes); + }; - const getLessons = () => { - const lessons: NonNullable< - NonNullable["sections"] - >[0]["lesson"] = []; - course?.sections?.map((section) => - section.lesson?.map((lesson) => lessons.push(lesson)) - ); - return lessons; - }; + const getLessons = () => { + const lessons: NonNullable< + NonNullable["sections"] + >[0]["lesson"] = []; + course?.sections?.map((section) => + section.lesson?.map((lesson) => lessons.push(lesson)), + ); + return lessons; + }; - const lessonIndex = getLessons().findIndex((l) => l.slug === lesson.slug); - const lessonNoContent = getLessons()[lessonIndex]; + const lessonIndex = getLessons().findIndex((l) => l.slug === lesson.slug); + const lessonNoContent = getLessons()[lessonIndex]; - const main = () => { - return ( -
-
-
-

{lesson?.title}

-
- -
-
- -
-
-
- -

Bookmark

-
-
- {lessonIndex > 0 && ( - - )} - - {lessonIndex < getLessons().length - 1 && ( - - )} -
-
- -

Complete

-
-
-
- ); - }; + const main = () => { + return ( +
+
+
+

{lesson?.title}

+
+ +
+
+ +
+
+
+ +

Bookmark

+
+
+ {lessonIndex > 0 && ( + + )} + + {lessonIndex < getLessons().length - 1 && ( + + )} +
+
+ +

Complete

+
+
+
+ ); + }; - return ( - <> -
- - - {course?.sections && ( - <> -
- - {course.title} - -
+ return ( + <> +
+ + + {course?.sections && ( + <> +
+ + {course.title} + +
-
- -
- - )} -
- - - {main()} - -
-
-
- {main()} - - {course?.sections && ( - <> -
- -
- - )} -
-
- - ); +
+ +
+ + )} +
+ + + {main()} + +
+
+
+ {main()} + + {course?.sections && ( + <> +
+ +
+ + )} +
+
+ + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/nav-lesson.tsx b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/nav-lesson.tsx index 1d674580..d91f4925 100644 --- a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/nav-lesson.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/nav-lesson.tsx @@ -1,6 +1,6 @@ "use client"; -import type { LessonsInCourseQueryResult } from "@/sanity.types"; +import type { LessonsInCourseQueryResult } from "@/sanity/types"; import Link from "next/link"; import { useActivePath } from "@/lib/hooks"; @@ -9,48 +9,48 @@ import BadgePro from "@/components/badge-pro"; import LessonComplete from "./lesson-complete"; interface Props { - course: LessonsInCourseQueryResult | undefined; + course: LessonsInCourseQueryResult | undefined; } export default function NavLesson({ course }: Props) { - const checkActivePath = useActivePath(); - return ( - <> - {course?.sections?.map((section, i) => ( -
- {section?.title && ( -
-

- {section.title} -

- -
- )} - {section.lesson?.map((l) => ( -
- + const checkActivePath = useActivePath(); + return ( + <> + {course?.sections?.map((section, i) => ( +
+ {section?.title && ( +
+

+ {section.title} +

+ +
+ )} + {section.lesson?.map((l) => ( +
+ - - {l.title} - -
- -
-
- ))} -
- ))} - - ); + prefetch={false} + > + {l.title} + +
+ +
+
+ ))} +
+ ))} + + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/page.tsx b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/page.tsx index 7795932e..ed00c8e7 100644 --- a/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/page.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lesson/[lessonSlug]/page.tsx @@ -5,111 +5,110 @@ import { notFound, redirect } from "next/navigation"; import { Suspense } from "react"; import type { - LessonQueryResult, - LessonsInCourseQueryResult, -} from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; + LessonQueryResult, + LessonsInCourseQueryResult, +} from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { lessonQuery, lessonsInCourseQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import LessonPanelClientOnly from "./lesson-client-only"; import MoreContent from "@/components/more-content"; import MoreHeader from "@/components/more-header"; import PortableText from "@/components/portable-text"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { cookies } from "next/headers"; import { jwtDecode } from "jwt-decode"; -import { Idt } from "@/lib/firebase.types"; +import type { Idt } from "@/lib/firebase.types"; import { didUserPurchase } from "@/lib/server/firebase"; -type Props = { - params: { lessonSlug: string; courseSlug: string }; -}; +type Params = Promise<{ lessonSlug: string; courseSlug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const post = await sanityFetch({ - query: lessonQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(post?.coverImage); + const lesson = ( + await sanityFetch({ + query: lessonQuery, + params, + stega: false, + }) + ).data as LessonQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(lesson?.coverImage); - return { - authors: - post?.author?.map((a) => { - return { name: a.title }; - }) || [], - title: post?.title, - description: post?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + authors: + lesson?.author?.map((a) => { + return { name: a.title }; + }) || [], + title: lesson?.title, + description: lesson?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function LessonPage({ params }: Props) { - const [lesson, course] = await Promise.all([ - sanityFetch({ - query: lessonQuery, - params, - }), - sanityFetch({ - query: lessonsInCourseQuery, - params, - }), - ]); +export default async function LessonPage({ params }: { params: Params }) { + const [lesson, course] = ( + await Promise.all([ + sanityFetch({ query: lessonQuery, params }), + sanityFetch({ query: lessonsInCourseQuery, params }), + ]) + ).map((res) => res.data) as [ + LessonQueryResult, + NonNullable, + ]; - if (!lesson && !course) { - return notFound(); - } + if (!lesson && !course) { + return notFound(); + } - // Check if user is either a pro or paid for lesson - if (course?.stripeProduct && lesson?.locked) { - //First check if user session is valid - const cookieStore = cookies(); - const sessionCookie = cookieStore.get("app.at"); - if (!sessionCookie) return redirect(`/course/${course?.slug}?showPro=true`); - const jwtPayload = jwtDecode(sessionCookie?.value) as Idt; - if (!jwtPayload?.exp) - return redirect(`/course/${course?.slug}?showPro=true`); - const expiration = jwtPayload.exp; - const isExpired = expiration * 1000 < Date.now(); - if (isExpired) return redirect(`/course/${course?.slug}?showPro=true`); + // Check if user is either a pro or paid for lesson + if (course?.stripeProduct && lesson?.locked) { + //First check if user session is valid + const cookieStore = await cookies(); + const sessionCookie = cookieStore.get("app.at"); + if (!sessionCookie){ return redirect(`/course/${course?.slug}?showPro=true`);} + const jwtPayload = jwtDecode(sessionCookie?.value) as Idt; + if (!jwtPayload?.exp){ + return redirect(`/course/${course?.slug}?showPro=true`);} + const expiration = jwtPayload.exp; + const isExpired = expiration * 1000 < Date.now(); + if (isExpired){ return redirect(`/course/${course?.slug}?showPro=true`);} - //Check if user isn't pro - if (!jwtPayload?.stripeRole) { - const purchased = await didUserPurchase( - course.stripeProduct, - jwtPayload.user_id - ); - if (!purchased) return redirect(`/course/${course?.slug}?showPro=true`); - } - } + //Check if user isn't pro + if (!jwtPayload?.stripeRole) { + const purchased = await didUserPurchase( + course.stripeProduct, + jwtPayload.user_id, + ); + if (!purchased){ return redirect(`/course/${course?.slug}?showPro=true`);} + } + } - return ( - <> - {lesson?._id && course?._id && ( -
- Loading Lesson Panel...}> - - - {lesson?.content?.length && ( - - )} - -
- )} - - ); + return ( + <> + {lesson?._id && course?._id && ( +
+ Loading Lesson Panel...}> + + + {lesson?.content?.length && ( + + )} + +
+ )} + + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/lessons.tsx b/app/(main)/(course)/course/[courseSlug]/lessons.tsx index 8c4356e0..236a9fbc 100644 --- a/app/(main)/(course)/course/[courseSlug]/lessons.tsx +++ b/app/(main)/(course)/course/[courseSlug]/lessons.tsx @@ -1,96 +1,100 @@ import Link from "next/link"; import CoverImage from "@/components/cover-image"; -import type { LessonsInCourseQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { LessonsInCourseQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { lessonsInCourseQuery } from "@/sanity/lib/queries"; import { - Card, - CardContent, - CardFooter, - CardHeader, + Card, + CardContent, + CardFooter, + CardHeader, } from "@/components/ui/card"; import Buy from "@/components/user-buy"; import UserGoProButton from "@/components/user-go-pro-button"; export default async function Lessons(params: { courseSlug: string }) { - const course = await sanityFetch({ - query: lessonsInCourseQuery, - params, - }); - return ( - <> - {course?.sections && ( -
-
-

- Lessons -

- {course?.sections?.map((section, i) => ( -
-
-

{section?.title}

-
-
- {section?.lesson?.map((post) => { - const { - _id, - _type, - title, - slug, - coverImage, - excerpt, - locked, - } = post; - return ( - - - - - - - -

- - {title} - -

+ const { courseSlug } = await params; + const course = ( + await sanityFetch({ + query: lessonsInCourseQuery, + params: { courseSlug }, + }) + ).data as LessonsInCourseQueryResult; - {excerpt && ( -

- {excerpt} -

- )} -
- - {locked && course?.stripeProduct && course?.title && ( -
- - -
- )} -
-
- ); - })} -
-
- ))} -
- )} - - ); + return ( + <> + {course?.sections && ( +
+
+

+ Lessons +

+ {course?.sections?.map((section) => ( +
+
+

{section?.title}

+
+
+ {section?.lesson?.map((post) => { + const { + _id, + _type, + title, + slug, + coverImage, + excerpt, + locked, + } = post; + return ( + + + + + + + +

+ + {title} + +

+ + {excerpt && ( +

+ {excerpt} +

+ )} +
+ + {locked && course?.stripeProduct && course?.title && ( +
+ + +
+ )} +
+
+ ); + })} +
+
+ ))} +
+ )} + + ); } diff --git a/app/(main)/(course)/course/[courseSlug]/page.tsx b/app/(main)/(course)/course/[courseSlug]/page.tsx index 7d4b444c..d4729f28 100644 --- a/app/(main)/(course)/course/[courseSlug]/page.tsx +++ b/app/(main)/(course)/course/[courseSlug]/page.tsx @@ -9,8 +9,8 @@ import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import PortableText from "@/components/portable-text"; -import type { CourseQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { CourseQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { courseQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import Lessons from "./lessons"; @@ -22,112 +22,117 @@ import ShowPro from "./show-pro"; import UserGoProButton from "@/components/user-go-pro-button"; import CarbonAdBanner from "@/components/carbon-ad-banner"; -type Props = { - params: { courseSlug: string }; -}; +type Params = Promise<{ courseSlug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const course = await sanityFetch({ - query: courseQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(course?.coverImage); + const { courseSlug } = await params; + const course = ( + await sanityFetch({ + query: courseQuery, + params: { courseSlug }, + stega: false, + }) + ).data as CourseQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(course?.coverImage); - return { - authors: - course?.author?.map((a) => { - return { name: a.title }; - }) || [], - title: course?.title, - description: course?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + authors: + course?.author?.map((a) => { + return { name: a.title }; + }) || [], + title: course?.title, + description: course?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function CoursePage({ params }: Props) { - const [course] = await Promise.all([ - sanityFetch({ - query: courseQuery, - params, - }), - ]); +export default async function CoursePage({ params }: { params: Params }) { + const { courseSlug } = await params; - if (!course?._id) { - return notFound(); - } + const course = ( + await sanityFetch({ + query: courseQuery, + params: { courseSlug }, + stega: false, + }) + ).data as CourseQueryResult; - return ( -
- - -
-

- {course.title} -

-
- -
-
-
- {course.author && ( -
- {course.author.map((a) => ( - - ))} -
- )} -
-
- -
-
- {course?.stripeProduct && course?.title && ( -
- - -
- )} -
-
- {course.content?.length && ( - - )} -
- -
-
- - - - -
- ); + if (!course?._id) { + return notFound(); + } + + return ( +
+ + +
+

+ {course.title} +

+
+ +
+
+
+ {course.author && ( +
+ {course.author.map((a) => ( + + ))} +
+ )} +
+
+ +
+
+ {course?.stripeProduct && course?.title && ( +
+ + +
+ )} +
+
+ {course.content?.length && ( + + )} +
+ +
+
+ + + + +
+ ); } diff --git a/app/(main)/(course)/course/[courseSlug]/show-pro.tsx b/app/(main)/(course)/course/[courseSlug]/show-pro.tsx index 939dc5c3..61acc049 100644 --- a/app/(main)/(course)/course/[courseSlug]/show-pro.tsx +++ b/app/(main)/(course)/course/[courseSlug]/show-pro.tsx @@ -5,18 +5,18 @@ import { usePathname, useSearchParams, useRouter } from "next/navigation"; import { useState, useEffect } from "react"; export default function ShowPro() { - const [showGoPro, setShowGoPro] = useState(false); - const searchParams = useSearchParams(); - const router = useRouter(); - const pathname = usePathname(); - const showPro = searchParams.get("showPro"); - useEffect(() => { - if (showPro) { - router.replace(pathname); - setShowGoPro(true); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [showPro]); + const [showGoPro, setShowGoPro] = useState(false); + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); + const showPro = searchParams.get("showPro"); + useEffect(() => { + if (showPro) { + router.replace(pathname); + setShowGoPro(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showPro]); - return <>{showGoPro && }; + return <>{showGoPro && }; } diff --git a/app/(main)/(course)/courses/page.tsx b/app/(main)/(course)/courses/page.tsx index c323f13a..e5924c3f 100644 --- a/app/(main)/(course)/courses/page.tsx +++ b/app/(main)/(course)/courses/page.tsx @@ -7,90 +7,91 @@ import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import Onboarding from "@/components/onboarding"; -import type { CoursesQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { CoursesQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { coursesQuery } from "@/sanity/lib/queries"; import MoreHeader from "@/components/more-header"; import CarbonAdBanner from "@/components/carbon-ad-banner"; function HeroCourse({ - title, - slug, - excerpt, - coverImage, - date, - author, + title, + slug, + excerpt, + coverImage, + date, + author, }: Pick< - Exclude, - "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" + Exclude, + "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" >) { - return ( -
- - - -
-
-

- - {title} - -

-
- -
-
-
- {excerpt && ( -

- {excerpt} -

- )} - {author && ( -
- {author.map((a) => ( - - ))} -
- )} -
-
-
- ); + return ( +
+ + + +
+
+

+ + {title} + +

+
+ +
+
+
+ {excerpt && ( +

+ {excerpt} +

+ )} + {author && ( +
+ {author.map((a) => ( + + ))} +
+ )} +
+
+
+ ); } export default async function Page() { - const [heroPost] = await Promise.all([ - sanityFetch({ query: coursesQuery }), - ]); - return ( -
- {heroPost ? ( - - ) : ( - - )} - - {heroPost?._id && ( - - )} -
- ); + const [heroPost] = ( + await Promise.all([sanityFetch({ query: coursesQuery })]) + ).map((res) => res.data) as [CoursesQueryResult]; + + return ( +
+ {heroPost ? ( + + ) : ( + + )} + + {heroPost?._id && ( + + )} +
+ ); } diff --git a/app/(main)/(course)/courses/page/[num]/page.tsx b/app/(main)/(course)/courses/page/[num]/page.tsx index 5a622f3d..0bb9d0c5 100644 --- a/app/(main)/(course)/courses/page/[num]/page.tsx +++ b/app/(main)/(course)/courses/page/[num]/page.tsx @@ -1,40 +1,40 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "course", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const [count] = ( + await Promise.all([ + sanityFetch({ + query: docCount, + params: { + type: "course", + }, + }), + ]) + ).map((res) => res.data) as [DocCountResult]; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const { num } = await params; + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/app/(main)/(course)/courses/page/page.tsx b/app/(main)/(course)/courses/page/page.tsx index 8ef287b6..f58ee0d1 100644 --- a/app/(main)/(course)/courses/page/page.tsx +++ b/app/(main)/(course)/courses/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/courses/page/1"); + redirect("/courses/page/1"); } diff --git a/app/(main)/(course)/courses/rss.json/route.ts b/app/(main)/(course)/courses/rss.json/route.ts index 758f0f49..2ccbe249 100644 --- a/app/(main)/(course)/courses/rss.json/route.ts +++ b/app/(main)/(course)/courses/rss.json/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.course, - }); - return new Response(feed.json1(), { - headers: { - "content-type": "application/json", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.course, + }); + return new Response(feed.json1(), { + headers: { + "content-type": "application/json", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(course)/courses/rss.xml/route.ts b/app/(main)/(course)/courses/rss.xml/route.ts index 1365e3c9..0cb7df89 100644 --- a/app/(main)/(course)/courses/rss.xml/route.ts +++ b/app/(main)/(course)/courses/rss.xml/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.course, - }); - return new Response(feed.rss2(), { - headers: { - "content-type": "text/xml", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.course, + }); + return new Response(feed.rss2(), { + headers: { + "content-type": "text/xml", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(guest)/guest/[slug]/page.tsx b/app/(main)/(guest)/guest/[slug]/page.tsx index 2182ee22..da7fbf04 100644 --- a/app/(main)/(guest)/guest/[slug]/page.tsx +++ b/app/(main)/(guest)/guest/[slug]/page.tsx @@ -5,10 +5,10 @@ import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; import type { - GuestQueryResult, - GuestQueryWithRelatedResult, -} from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; + GuestQueryResult, + GuestQueryWithRelatedResult, +} from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { guestQuery, guestQueryWithRelated } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import CoverMedia from "@/components/cover-media"; @@ -18,83 +18,85 @@ import UserSocials from "@/components/user-socials"; import UserRelated from "@/components/user-related"; import Avatar from "@/components/avatar"; -type Props = { - params: { slug: string }; -}; +type Params = Promise<{ slug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const guest = await sanityFetch({ - query: guestQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(guest?.coverImage); + const { slug } = await params; - return { - title: guest?.title, - description: guest?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + const guest = ( + await sanityFetch({ + query: guestQuery, + params: { slug }, + stega: false, + }) + ).data as GuestQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(guest?.coverImage); + + return { + title: guest?.title, + description: guest?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function GuestPage({ params }: Props) { - const [guest] = await Promise.all([ - sanityFetch({ - query: guestQueryWithRelated, - params, - }), - ]); +export default async function GuestPage({ params }: { params: Params }) { + const { slug } = await params; + + const [guest] = ( + await Promise.all([ + sanityFetch({ + query: guestQueryWithRelated, + params: { slug }, + }), + ]) + ).map((res) => res.data) as [GuestQueryWithRelatedResult]; - if (!guest?._id) { - return notFound(); - } + if (!guest?._id) { + return notFound(); + } - return ( -
- -
-
- {guest?.coverImage && ( -
- -
- )} -
-

- {guest.title} -

- {guest?.socials && ( -
- -
- )} -
-
-
- {guest.content?.length && ( - - )} -
-
- -
- ); + return ( +
+ +
+
+ {guest?.coverImage && ( +
+ +
+ )} +
+

+ {guest.title} +

+ {guest?.socials && ( +
+ +
+ )} +
+
+
+ {guest.content?.length && ( + + )} +
+
+ +
+ ); } diff --git a/app/(main)/(guest)/guests/page.tsx b/app/(main)/(guest)/guests/page.tsx index 242041f4..c7cf17db 100644 --- a/app/(main)/(guest)/guests/page.tsx +++ b/app/(main)/(guest)/guests/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/guests/page/1"); + redirect("/guests/page/1"); } diff --git a/app/(main)/(guest)/guests/page/[num]/page.tsx b/app/(main)/(guest)/guests/page/[num]/page.tsx index b4d6739d..877e3870 100644 --- a/app/(main)/(guest)/guests/page/[num]/page.tsx +++ b/app/(main)/(guest)/guests/page/[num]/page.tsx @@ -1,40 +1,40 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "guest", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const [count] = ( + await Promise.all([ + sanityFetch({ + query: docCount, + params: { + type: "guest", + }, + }), + ]) + ).map((res) => res.data) as [DocCountResult]; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const { num } = await params; + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/app/(main)/(guest)/guests/page/page.tsx b/app/(main)/(guest)/guests/page/page.tsx index 242041f4..c7cf17db 100644 --- a/app/(main)/(guest)/guests/page/page.tsx +++ b/app/(main)/(guest)/guests/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/guests/page/1"); + redirect("/guests/page/1"); } diff --git a/app/(main)/(podcast)/podcast/[slug]/page.tsx b/app/(main)/(podcast)/podcast/[slug]/page.tsx index f1c8cb7e..f3d2f14d 100644 --- a/app/(main)/(podcast)/podcast/[slug]/page.tsx +++ b/app/(main)/(podcast)/podcast/[slug]/page.tsx @@ -1,5 +1,5 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import { Suspense } from "react"; @@ -7,8 +7,8 @@ import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import PortableText from "@/components/portable-text"; -import type { PodcastQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PodcastQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { podcastQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import CoverMedia from "@/components/cover-media"; @@ -23,154 +23,160 @@ import PodcastOpenApple from "@/components/podcast-open-apple"; import PodcastOpenYouTube from "@/components/podcast-open-youtube"; import CarbonAdBanner from "@/components/carbon-ad-banner"; -type Props = { - params: { slug: string }; -}; +type Params = Promise<{ slug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const podcast = await sanityFetch({ - query: podcastQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(podcast?.coverImage); - - return { - authors: - podcast?.author?.map((a) => { - return { name: a.title }; - }) || [], - title: podcast?.title, - description: podcast?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + const { slug } = await params; + + const podcast = ( + await sanityFetch({ + query: podcastQuery, + params: { slug }, + stega: false, + }) + ).data as PodcastQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(podcast?.coverImage); + + return { + authors: + podcast?.author?.map((a) => { + return { name: a.title }; + }) || [], + title: podcast?.title, + description: podcast?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function PodcastPage({ params }: Props) { - const [podcast] = await Promise.all([ - sanityFetch({ - query: podcastQuery, - params, - }), - ]); - - if (!podcast?._id) { - return notFound(); - } - - const src = podcast?.spotify?.enclosures?.at(0)?.url; - - return ( -
- -
-

- {podcast.title} -

- -
- -
- -
-
-
-
- {(podcast?.author || podcast?.guest) && ( -
- {podcast?.author?.map((a) => ( - - ))} - {podcast?.guest?.map((a) => ( - - ))} -
- )} -
-
- -
-
- -
- - {src && ( -
-

Listening Options

- - or -
- - - -
-
- )} -
- {podcast?.sponsor?.length && ( -
-

Sponsors

-
-
- -
-
-
- )} - - {podcast?.content?.length && ( - - )} -
- {podcast?.pick?.length && ( - <> -
-
-

- Picks -

-
- -
-
- -
-
- - )} - -
- ); +export default async function PodcastPage({ params }: { params: Params }) { + const { slug } = await params; + + const [podcast] = ( + await Promise.all([ + sanityFetch({ + query: podcastQuery, + params: { slug }, + }), + ]) + ).map((res) => res.data) as [PodcastQueryResult]; + + if (!podcast?._id) { + return notFound(); + } + + const src = podcast?.spotify?.enclosures?.at(0)?.url; + + return ( +
+ +
+

+ {podcast.title} +

+ +
+ +
+ +
+
+
+
+ {(podcast?.author || podcast?.guest) && ( +
+ {podcast?.author?.map((a) => ( + + ))} + {podcast?.guest?.map((a) => ( + + ))} +
+ )} +
+
+ +
+
+ +
+ + {src && ( +
+

+ Listening Options +

+ + or +
+ + + +
+
+ )} +
+ {podcast?.sponsor?.length && ( +
+

Sponsors

+
+
+ +
+
+
+ )} + + {podcast?.content?.length && ( + + )} +
+ {podcast?.pick?.length && ( + <> +
+
+

+ Picks +

+
+ +
+
+ +
+
+ + )} + +
+ ); } diff --git a/app/(main)/(podcast)/podcast/[slug]/picks.tsx b/app/(main)/(podcast)/podcast/[slug]/picks.tsx index 20fd4064..ea1805af 100644 --- a/app/(main)/(podcast)/podcast/[slug]/picks.tsx +++ b/app/(main)/(podcast)/podcast/[slug]/picks.tsx @@ -1,88 +1,88 @@ import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; -import { PodcastQueryResult } from "@/sanity.types"; +import type { PodcastQueryResult } from "@/sanity/types"; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from "@/components/ui/table"; import Link from "next/link"; import { FaExternalLinkSquareAlt } from "react-icons/fa"; export default async function PodcastPage({ - picks, + picks, }: { - picks: NonNullable["pick"]>; + picks: NonNullable["pick"]>; }) { - const groupedPicks = picks.reduce( - (acc, pick) => { - const author = pick?.user?.title; - if (!author) { - return acc; - } - if (!acc?.[author]) { - acc[author] = []; - } - acc[author].push(pick); - return acc; - }, - {} as Record - ); + const groupedPicks = picks.reduce( + (acc, pick) => { + const author = pick?.user?.title; + if (!author) { + return acc; + } + if (!acc?.[author]) { + acc[author] = []; + } + acc[author].push(pick); + return acc; + }, + {} as Record, + ); - const sortedPicks = Object.entries(groupedPicks).sort(([userA], [userB]) => - userA.localeCompare(userB) - ); + const sortedPicks = Object.entries(groupedPicks).sort(([userA], [userB]) => + userA.localeCompare(userB), + ); - return ( - <> - {sortedPicks.map(([author, picksByAuthor]) => ( - - -
- - {author} - -
-
- - - - - - Picks - - - - - {picksByAuthor.map((pick) => ( - - - -
-
{pick.name}
- -
- -
-
- ))} -
-
-
-
- ))} - - ); + return ( + <> + {sortedPicks.map(([author, picksByAuthor]) => ( + + +
+ + {author} + +
+
+ + + + + + Picks + + + + + {picksByAuthor.map((pick) => ( + + + +
+
{pick.name}
+ +
+ +
+
+ ))} +
+
+
+
+ ))} + + ); } diff --git a/app/(main)/(podcast)/podcasts/page.tsx b/app/(main)/(podcast)/podcasts/page.tsx index 8f4d14a9..cf9e86d8 100644 --- a/app/(main)/(podcast)/podcasts/page.tsx +++ b/app/(main)/(podcast)/podcasts/page.tsx @@ -7,8 +7,8 @@ import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import Onboarding from "@/components/onboarding"; -import type { PodcastsQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PodcastsQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { podcastsQuery } from "@/sanity/lib/queries"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -16,93 +16,97 @@ import MoreHeader from "@/components/more-header"; import CarbonAdBanner from "@/components/carbon-ad-banner"; function HeroPodcast({ - title, - slug, - excerpt, - coverImage, - date, - author, - guest, + title, + slug, + excerpt, + coverImage, + date, + author, + guest, }: Pick< - Exclude, - "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" | "guest" + Exclude, + "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" | "guest" >) { - return ( -
- - - -
-
-

- - {title} - -

-
- -
-
-
- {excerpt && ( -

- {excerpt} -

- )} - {(author || guest) && ( -
- {author?.map((a) => ( - - ))} - {guest?.map((a) => ( - - ))} -
- )} -
-
-
- ); + return ( +
+ + + +
+
+

+ + {title} + +

+
+ +
+
+
+ {excerpt && ( +

+ {excerpt} +

+ )} + {(author || guest) && ( +
+ {author?.map((a) => ( + + ))} + {guest?.map((a) => ( + + ))} +
+ )} +
+
+
+ ); } export default async function Page() { - const [heroPost] = await Promise.all([ - sanityFetch({ query: podcastsQuery }), - ]); - return ( -
- {heroPost ? ( - - ) : ( - - )} - - {heroPost?._id && ( - - )} -
- ); + const [heroPost] = ( + await Promise.all([ + sanityFetch({ + query: podcastsQuery, + }), + ]) + ).map((res) => res.data) as [PodcastsQueryResult]; + return ( +
+ {heroPost ? ( + + ) : ( + + )} + + {heroPost?._id && ( + + )} +
+ ); } diff --git a/app/(main)/(podcast)/podcasts/page/[num]/page.tsx b/app/(main)/(podcast)/podcasts/page/[num]/page.tsx index 61a724f5..f920156b 100644 --- a/app/(main)/(podcast)/podcasts/page/[num]/page.tsx +++ b/app/(main)/(podcast)/podcasts/page/[num]/page.tsx @@ -1,40 +1,40 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "podcast", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const [count] = ( + await Promise.all([ + sanityFetch({ + query: docCount, + params: { + type: "podcast", + }, + }), + ]) + ).map((res) => res.data) as [DocCountResult]; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const { num } = await params; + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/app/(main)/(podcast)/podcasts/page/page.tsx b/app/(main)/(podcast)/podcasts/page/page.tsx index a3ee6915..915dc084 100644 --- a/app/(main)/(podcast)/podcasts/page/page.tsx +++ b/app/(main)/(podcast)/podcasts/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/podcasts/page/1"); + redirect("/podcasts/page/1"); } diff --git a/app/(main)/(podcast)/podcasts/rss.json/route.ts b/app/(main)/(podcast)/podcasts/rss.json/route.ts index fb4810cd..d635242a 100644 --- a/app/(main)/(podcast)/podcasts/rss.json/route.ts +++ b/app/(main)/(podcast)/podcasts/rss.json/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.podcast, - }); - return new Response(feed.json1(), { - headers: { - "content-type": "application/json", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.podcast, + }); + return new Response(feed.json1(), { + headers: { + "content-type": "application/json", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(podcast)/podcasts/rss.xml/route.ts b/app/(main)/(podcast)/podcasts/rss.xml/route.ts index 4714de78..2b93d7a8 100644 --- a/app/(main)/(podcast)/podcasts/rss.xml/route.ts +++ b/app/(main)/(podcast)/podcasts/rss.xml/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.podcast, - }); - return new Response(feed.rss2(), { - headers: { - "content-type": "text/xml", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.podcast, + }); + return new Response(feed.rss2(), { + headers: { + "content-type": "text/xml", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(post)/blog/page.tsx b/app/(main)/(post)/blog/page.tsx index 531ee4d3..6a9ea525 100644 --- a/app/(main)/(post)/blog/page.tsx +++ b/app/(main)/(post)/blog/page.tsx @@ -7,8 +7,8 @@ import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import Onboarding from "@/components/onboarding"; -import type { BlogQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { BlogQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { blogQuery } from "@/sanity/lib/queries"; import { Button, buttonVariants } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -16,83 +16,83 @@ import CarbonAdBanner from "@/components/carbon-ad-banner"; import MoreHeader from "@/components/more-header"; function HeroPost({ - title, - slug, - excerpt, - coverImage, - date, - author, + title, + slug, + excerpt, + coverImage, + date, + author, }: Pick< - Exclude, - "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" + Exclude, + "title" | "coverImage" | "date" | "excerpt" | "author" | "slug" >) { - return ( -
- - - -
-
-

- - {title} - -

-
- -
-
-
- {excerpt && ( -

- {excerpt} -

- )} - {author && ( -
- {author.map((a) => ( - - ))} -
- )} -
-
-
- ); + return ( +
+ + + +
+
+

+ + {title} + +

+
+ +
+
+
+ {excerpt && ( +

+ {excerpt} +

+ )} + {author && ( +
+ {author.map((a) => ( + + ))} +
+ )} +
+
+
+ ); } export default async function Page() { - const [heroPost] = await Promise.all([ - sanityFetch({ query: blogQuery }), - ]); - return ( -
- {heroPost ? ( - - ) : ( - - )} - - {heroPost?._id && ( - - )} -
- ); + const [heroPost] = ( + await Promise.all([sanityFetch({ query: blogQuery })]) + ).map((res) => res.data) as [BlogQueryResult]; + return ( +
+ {heroPost ? ( + + ) : ( + + )} + + {heroPost?._id && ( + + )} +
+ ); } diff --git a/app/(main)/(post)/blog/page/[num]/page.tsx b/app/(main)/(post)/blog/page/[num]/page.tsx index ef3e6dac..1595e67b 100644 --- a/app/(main)/(post)/blog/page/[num]/page.tsx +++ b/app/(main)/(post)/blog/page/[num]/page.tsx @@ -1,35 +1,35 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "post", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const [count] = ( + await Promise.all([ + sanityFetch({ + query: docCount, + params: { + type: "post", + }, + }), + ]) + ).map((res) => res.data) as [DocCountResult]; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const { num } = await params; + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/app/(main)/(post)/blog/page/page.tsx b/app/(main)/(post)/blog/page/page.tsx index 563b0459..f5d45011 100644 --- a/app/(main)/(post)/blog/page/page.tsx +++ b/app/(main)/(post)/blog/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/blog/page/1"); + redirect("/blog/page/1"); } diff --git a/app/(main)/(post)/blog/rss.json/route.ts b/app/(main)/(post)/blog/rss.json/route.ts index e2c91f83..afb655a8 100644 --- a/app/(main)/(post)/blog/rss.json/route.ts +++ b/app/(main)/(post)/blog/rss.json/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.post, - }); - return new Response(feed.json1(), { - headers: { - "content-type": "application/json", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.post, + }); + return new Response(feed.json1(), { + headers: { + "content-type": "application/json", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(post)/blog/rss.xml/route.ts b/app/(main)/(post)/blog/rss.xml/route.ts index a68848cc..4f76d3b3 100644 --- a/app/(main)/(post)/blog/rss.xml/route.ts +++ b/app/(main)/(post)/blog/rss.xml/route.ts @@ -4,13 +4,13 @@ import { buildFeed } from "@/lib/rss"; import { ContentType } from "@/lib/types"; export async function GET() { - const feed = await buildFeed({ - type: ContentType.post, - }); - return new Response(feed.rss2(), { - headers: { - "content-type": "text/xml", - "cache-control": "max-age=0, s-maxage=3600", - }, - }); + const feed = await buildFeed({ + type: ContentType.post, + }); + return new Response(feed.rss2(), { + headers: { + "content-type": "text/xml", + "cache-control": "max-age=0, s-maxage=3600", + }, + }); } diff --git a/app/(main)/(post)/post/[slug]/page.tsx b/app/(main)/(post)/post/[slug]/page.tsx index 826fccf2..1e139434 100644 --- a/app/(main)/(post)/post/[slug]/page.tsx +++ b/app/(main)/(post)/post/[slug]/page.tsx @@ -3,19 +3,13 @@ import { groq, type PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import { Suspense } from "react"; -//Google Ads -import dynamic from "next/dynamic"; -const GoogleAdBanner = dynamic(() => import("@/components/google-ad-banner"), { - ssr: false, -}); - import Avatar from "@/components/avatar"; import DateComponent from "@/components/date"; import MoreContent from "@/components/more-content"; import PortableText from "@/components/portable-text"; -import type { PostQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PostQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { postQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import CoverMedia from "@/components/cover-media"; @@ -24,108 +18,112 @@ import { BreadcrumbLinks } from "@/components/breadrumb-links"; import SponsorCard from "@/components/sponsor-card"; import CarbonAdBanner from "@/components/carbon-ad-banner"; -type Props = { - params: { slug: string }; -}; +type Params = Promise<{ slug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const post = await sanityFetch({ - query: postQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(post?.coverImage); + const { slug } = await params; + + const post = ( + await sanityFetch({ + query: postQuery, + params: { slug }, + stega: false, + }) + ).data as PostQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(post?.coverImage); - return { - authors: - post?.author?.map((a) => { - return { name: a.title }; - }) || [], - title: post?.title, - description: post?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + authors: + post?.author?.map((a) => { + return { name: a.title }; + }) || [], + title: post?.title, + description: post?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function PostPage({ params }: Props) { - const [post] = await Promise.all([ - sanityFetch({ - query: postQuery, - params, - }), - ]); +export default async function PostPage({ params }: { params: Params }) { + const { slug } = await params; + + const [post] = ( + await Promise.all([ + sanityFetch({ + query: postQuery, + params: { slug }, + }), + ]) + ).map((res) => res.data) as [PostQueryResult]; - if (!post?._id) { - return notFound(); - } + if (!post?._id) { + return notFound(); + } - return ( -
- -
-

- {post.title} -

-
- -
-
-
-
- {(post?.author) && ( -
- {post?.author?.map((a) => ( - - ))} -
- )} -
-
- -
-
- -
- {post?.sponsor?.length && ( -
-

Sponsors

-
-
- -
-
-
- )} - {post.content?.length && ( - - )} -
- -
- ); + return ( +
+ +
+

+ {post.title} +

+
+ +
+
+
+
+ {post?.author && ( +
+ {post?.author?.map((a) => ( + + ))} +
+ )} +
+
+ +
+
+ +
+ {post?.sponsor?.length && ( +
+

Sponsors

+
+
+ +
+
+
+ )} + {post.content?.length && ( + + )} +
+ +
+ ); } diff --git a/app/(main)/(sponsor)/sponsor/[slug]/page.tsx b/app/(main)/(sponsor)/sponsor/[slug]/page.tsx index ddb869c8..711a0df2 100644 --- a/app/(main)/(sponsor)/sponsor/[slug]/page.tsx +++ b/app/(main)/(sponsor)/sponsor/[slug]/page.tsx @@ -5,10 +5,10 @@ import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; import type { - SponsorQueryResult, - SponsorQueryWithRelatedResult, -} from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; + SponsorQueryResult, + SponsorQueryWithRelatedResult, +} from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { sponsorQuery, sponsorQueryWithRelated } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import CoverMedia from "@/components/cover-media"; @@ -17,76 +17,80 @@ import { BreadcrumbLinks } from "@/components/breadrumb-links"; import UserSocials from "@/components/user-socials"; import UserRelated from "@/components/user-related"; -type Props = { - params: { slug: string }; -}; +type Params = Promise<{ slug: string }>; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params }: { params: Params }, + parent: ResolvingMetadata, ): Promise { - const sponsor = await sanityFetch({ - query: sponsorQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(sponsor?.coverImage); + const { slug } = await params; - return { - title: sponsor?.title, - description: sponsor?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + const sponsor = ( + await sanityFetch({ + query: sponsorQuery, + params: { slug }, + stega: false, + }) + ).data as SponsorQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(sponsor?.coverImage); + + return { + title: sponsor?.title, + description: sponsor?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function SponsorPage({ params }: Props) { - const [sponsor] = await Promise.all([ - sanityFetch({ - query: sponsorQueryWithRelated, - params, - }), - ]); +export default async function SponsorPage({ params }: { params: Params }) { + const { slug } = await params; + + const [sponsor] = ( + await Promise.all([ + sanityFetch({ + query: sponsorQueryWithRelated, + params: { slug }, + }), + ]) + ).map((res) => res.data) as [SponsorQueryWithRelatedResult]; - if (!sponsor?._id) { - return notFound(); - } + if (!sponsor?._id) { + return notFound(); + } - return ( -
- -
- -
-

- {sponsor.title} -

- {sponsor?.socials && ( -
- -
- )} -
-
- {sponsor.content?.length && ( - - )} -
-
- -
- ); + return ( +
+ +
+ +
+

+ {sponsor.title} +

+ {sponsor?.socials && ( +
+ +
+ )} +
+
+ {sponsor.content?.length && ( + + )} +
+
+ +
+ ); } diff --git a/app/(main)/(sponsor)/sponsors/page.tsx b/app/(main)/(sponsor)/sponsors/page.tsx index 25f7a605..b5f37406 100644 --- a/app/(main)/(sponsor)/sponsors/page.tsx +++ b/app/(main)/(sponsor)/sponsors/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/sponsors/page/1"); + redirect("/sponsors/page/1"); } diff --git a/app/(main)/(sponsor)/sponsors/page/[num]/page.tsx b/app/(main)/(sponsor)/sponsors/page/[num]/page.tsx index 89288963..c6f74fb7 100644 --- a/app/(main)/(sponsor)/sponsors/page/[num]/page.tsx +++ b/app/(main)/(sponsor)/sponsors/page/[num]/page.tsx @@ -1,40 +1,40 @@ import MoreContent from "@/components/more-content"; -import { DocCountResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { DocCountResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import PaginateList from "@/components/paginate-list"; import { docCount } from "@/sanity/lib/queries"; const LIMIT = 10; -type Props = { - params: { num: string }; -}; +type Params = Promise<{ num: string }>; -export default async function Page({ params }: Props) { - const [count] = await Promise.all([ - sanityFetch({ - query: docCount, - params: { - type: "sponsor", - }, - }), - ]); +export default async function Page({ params }: { params: Params }) { + const [count] = ( + await Promise.all([ + sanityFetch({ + query: docCount, + params: { + type: "sponsor", + }, + }), + ]) + ).map((res) => res.data) as [DocCountResult]; - const { num } = params; - const pageNumber = Number(num); - const offset = (pageNumber - 1) * LIMIT; - const limit = offset + LIMIT; + const { num } = await params; + const pageNumber = Number(num); + const offset = (pageNumber - 1) * LIMIT; + const limit = offset + LIMIT; - return ( -
- - -
- ); + return ( +
+ + +
+ ); } diff --git a/app/(main)/(sponsor)/sponsors/page/page.tsx b/app/(main)/(sponsor)/sponsors/page/page.tsx index 25f7a605..b5f37406 100644 --- a/app/(main)/(sponsor)/sponsors/page/page.tsx +++ b/app/(main)/(sponsor)/sponsors/page/page.tsx @@ -1,5 +1,5 @@ import { redirect } from "next/navigation"; export default async function Page() { - redirect("/sponsors/page/1"); + redirect("/sponsors/page/1"); } diff --git a/app/(main)/(top-level-pages)/[slug]/page.tsx b/app/(main)/(top-level-pages)/[slug]/page.tsx index 0c05e2e7..7423e0fd 100644 --- a/app/(main)/(top-level-pages)/[slug]/page.tsx +++ b/app/(main)/(top-level-pages)/[slug]/page.tsx @@ -1,68 +1,78 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; type Props = { - params: { slug: string }; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const { slug } = await params; - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + const page = ( + await sanityFetch({ + query: pageQuery, + params: { slug }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); + + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function PagePage({ params }: Props) { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params, - }), - ]); +export default async function PagePage({ params, searchParams }: Props) { + const { slug } = await params; + + const [page] = ( + await Promise.all([ + sanityFetch({ + query: pageQuery, + params: { slug }, + stega: false, + }), + ]) + ).map((res) => res.data) as [PageQueryResult]; - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - return ( -
-
-
-

- {page.title} -

-
-
- {page.content?.length && ( - - )} -
-
-
- ); + return ( +
+
+
+

+ {page.title} +

+
+
+ {page.content?.length && ( + + )} +
+
+
+ ); } diff --git a/app/(main)/(top-level-pages)/pro/page.tsx b/app/(main)/(top-level-pages)/pro/page.tsx index c2848380..504ea44c 100644 --- a/app/(main)/(top-level-pages)/pro/page.tsx +++ b/app/(main)/(top-level-pages)/pro/page.tsx @@ -1,74 +1,79 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import ProBenefits from "@/components/pro-benefits"; import { Suspense } from "react"; type Props = { - params: false; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params: { - slug: "pro", - }, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "pro", + }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function ProPage() { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params: { - slug: "pro", - }, - }), - ]); +export default async function ProPage({ params, searchParams }: Props) { + const [page] = ( + await Promise.all([ + sanityFetch({ + query: pageQuery, + params: { + slug: "pro", + }, + }), + ]) + ).map((res) => res.data) as [PageQueryResult]; - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - return ( -
-
- {page.coverImage && - - - - } -
-
- {page.content?.length && ( - - )} -
-
- ); + return ( +
+
+ {page.coverImage && ( + + + + )} +
+
+ {page.content?.length && ( + + )} +
+
+ ); } diff --git a/app/(main)/(top-level-pages)/search/page.tsx b/app/(main)/(top-level-pages)/search/page.tsx index 3f35c4ac..7e61d96d 100644 --- a/app/(main)/(top-level-pages)/search/page.tsx +++ b/app/(main)/(top-level-pages)/search/page.tsx @@ -5,9 +5,9 @@ import AlgoliaSearch from "@/components/algolia-search"; export const dynamic = "force-dynamic"; export default function Page() { - return ( - Loading...}> - - - ); + return ( + Loading...}> + + + ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/blog/page.tsx b/app/(main)/(top-level-pages)/sponsorships/blog/page.tsx index c5c0b2c1..0d118589 100644 --- a/app/(main)/(top-level-pages)/sponsorships/blog/page.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/blog/page.tsx @@ -1,8 +1,8 @@ import type { Metadata, ResolvingMetadata } from "next"; import { notFound } from "next/navigation"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import { BreadcrumbLinks } from "@/components/breadrumb-links"; @@ -11,164 +11,170 @@ import SponsorshipForm from "../sponsorship-form"; import AJPrimary from "@/components/icons/aj-primary"; type Props = { - params: false; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params: { - slug: "blog", - }, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "blog", + }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function SponsorshipsPage() { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params: { - slug: "blog", - }, - }), - ]); +export default async function SponsorshipsPage({ + params, + searchParams, +}: Props) { + const [page] = ( + await Promise.all([ + sanityFetch({ + query: pageQuery, + params: { + slug: "blog", + }, + }), + ]) + ).map((res) => res.data) as [PageQueryResult]; - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - return ( -
- -
-
- -
-
-

- Sponsorship for - - {" "} - CodingCat.dev Blog{" "} - - - a CodingCat.dev Production - -

-
- -
-
-
-
-
-

- Why - would you sponsor our blog? -

-
-
-

- On CodingCat.dev your advertisement is{" "} - - permanent - - ! -

-

- You read that right, it is not just while you are sponsoring and - it doesn't change by the flavor of the week like Carbon or - Google Ads. -

+ return ( +
+ +
+
+ +
+
+

+ Sponsorship for + + {" "} + CodingCat.dev Blog{" "} + + + a CodingCat.dev Production + +

+
+ +
+
+
+
+
+

+ Why + would you sponsor our blog? +

+
+
+

+ On CodingCat.dev your advertisement is{" "} + + permanent + + ! +

+

+ You read that right, it is not just while you are sponsoring and + it doesn't change by the flavor of the week like Carbon or + Google Ads. +

-

- Blog sponsorship is a great way to reach a highly engaged - audience of potential customers. By sponsoring a blog post, your - company can be featured prominently on a popular blog, with the - opportunity to reach a large number of readers who are already - interested in the topics your post is about. -

-
-
-
+

+ Blog sponsorship is a great way to reach a highly engaged + audience of potential customers. By sponsoring a blog post, your + company can be featured prominently on a popular blog, with the + opportunity to reach a large number of readers who are already + interested in the topics your post is about. +

+
+
+
- + -
-
-
    -
  • -

    - Increased Brand Awareness and Visibility: - By sponsoring blog posts on CodingCat.dev, your brand will be - prominently featured in front of a highly engaged audience of - tech enthusiasts and programmers. This exposure can - significantly boost brand awareness and make your company more - recognizable in the tech industry. -

    -
  • -
  • -

    - Enhanced Brand Credibility and Reputation: - Being associated with a reputable blog like CodingCat.dev can - enhance your brand's credibility and reputation. The - blog's audience will associate your company with - high-quality content and expertise, fostering trust and - loyalty. -

    -
  • -
  • -

    - Targeted Audience Reach: - CodingCat.dev attracts a dedicated readership of individuals - passionate about coding and programming. Sponsoring blog posts - on this platform ensures you're reaching a highly - targeted audience of potential customers genuinely interested - in your products or services. -

    -
  • -
  • -

    - Long-Term Impact and Brand Recall: - Unlike traditional advertising that fades quickly, sponsoring - long-term blog posts on CodingCat.dev creates a lasting - impression. Your brand will remain visible and associated with - valuable content long after the initial publication date. -

    -
  • -
  • -

    - Cost-Effective Marketing Strategy: - Compared to traditional advertising methods, blog sponsorship - offers a cost-effective way to reach a large audience. The - targeted nature of blog readership ensures your marketing - efforts are reaching the right people, maximizing the return - on your investment. -

    -
  • -
-
-
- -
-
- ); +
+
+
    +
  • +

    + Increased Brand Awareness and Visibility: + By sponsoring blog posts on CodingCat.dev, your brand will be + prominently featured in front of a highly engaged audience of + tech enthusiasts and programmers. This exposure can + significantly boost brand awareness and make your company more + recognizable in the tech industry. +

    +
  • +
  • +

    + Enhanced Brand Credibility and Reputation: + Being associated with a reputable blog like CodingCat.dev can + enhance your brand's credibility and reputation. The + blog's audience will associate your company with + high-quality content and expertise, fostering trust and + loyalty. +

    +
  • +
  • +

    + Targeted Audience Reach: + CodingCat.dev attracts a dedicated readership of individuals + passionate about coding and programming. Sponsoring blog posts + on this platform ensures you're reaching a highly + targeted audience of potential customers genuinely interested + in your products or services. +

    +
  • +
  • +

    + Long-Term Impact and Brand Recall: + Unlike traditional advertising that fades quickly, sponsoring + long-term blog posts on CodingCat.dev creates a lasting + impression. Your brand will remain visible and associated with + valuable content long after the initial publication date. +

    +
  • +
  • +

    + Cost-Effective Marketing Strategy: + Compared to traditional advertising methods, blog sponsorship + offers a cost-effective way to reach a large audience. The + targeted nature of blog readership ensures your marketing + efforts are reaching the right people, maximizing the return + on your investment. +

    +
  • +
+
+
+ + + + ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/code-with-codingcatdev/page.tsx b/app/(main)/(top-level-pages)/sponsorships/code-with-codingcatdev/page.tsx index bfc15377..c4adee9d 100644 --- a/app/(main)/(top-level-pages)/sponsorships/code-with-codingcatdev/page.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/code-with-codingcatdev/page.tsx @@ -1,11 +1,11 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import { BreadcrumbLinks } from "@/components/breadrumb-links"; @@ -14,158 +14,164 @@ import SponsorshipForm from "../sponsorship-form"; import AJPrimary from "@/components/icons/aj-primary"; type Props = { - params: false; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params: { - slug: "code-with-codingcatdev", - }, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "code-with-codingcatdev", + }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function SponsorshipsPage() { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params: { - slug: "code-with-codingcatdev", - }, - }), - ]); +export default async function SponsorshipsPage({ + params, + searchParams, +}: Props) { + const [page] = ( + await Promise.all([ + sanityFetch({ + query: pageQuery, + params: { + slug: "code-with-codingcatdev", + }, + }), + ]) + ).map((res) => res.data) as [PageQueryResult]; - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - return ( -
- -
-
- -
-
-

- Sponsorship for - - {" "} - Code with CodingCat.dev{" "} - - - a CodingCat.dev Production - -

-
- -
-
-
-
-
-

- On CodingCat.dev your advertisement is{" "} - - permanent - - ! -

-

- You read that right, it is not just while you are sponsoring and - it doesn't change by the flavor of the week like Carbon or - Google Ads. -

+ return ( +
+ +
+
+ +
+
+

+ Sponsorship for + + {" "} + Code with CodingCat.dev{" "} + + + a CodingCat.dev Production + +

+
+ +
+
+
+
+
+

+ On CodingCat.dev your advertisement is{" "} + + permanent + + ! +

+

+ You read that right, it is not just while you are sponsoring and + it doesn't change by the flavor of the week like Carbon or + Google Ads. +

-

- Streaming sponsorship is a great way to reach a highly engaged - audience of potential customers. By sponsoring a stream, your - company can be featured prominently on the footer of the stream - of{" "} - - over 16K subscribers - - , with the opportunity to reach a large number of viewers who - are already interested in the topics your video is about. -

-
-
-
- -
-
-
-

- Why - - would you sponsor live coding? - -

-
-
    -
  • - Reach a large audience: - Live streaming on YouTube and Twitch is a great way to reach a - large audience. In fact, YouTube Live is one of the most popular - live streaming platforms in the world, with millions of viewers - tuning in each day. -
  • -
  • - Engage with your audience: - Live streaming is also a great way to engage with your audience. - You can interact with viewers in real time, answer questions, - and get feedback. This can help you build relationships with - your audience and create a sense of community. -
  • -
  • - Promote your brand: - Live streaming is a great way to promote your brand. You can use - live streaming to showcase your products or services, announce - new initiatives, or simply share your company culture. This can - help you raise awareness of your brand and attract new - customers. -
  • -
  • - Drive traffic to your website: - Live streaming can also help you drive traffic to your website. - You can include a link to your website in your live stream - description, or you can encourage viewers to visit your website - for more information. This can help you increase website traffic - and generate leads. -
  • -
-
-
-
-
-

- If you're looking for a way to reach a large audience, engage - with your audience, promote your brand, and drive traffic to your - website, then sponsoring live YouTube is a great option. -

-
-
- -
-
- ); +

+ Streaming sponsorship is a great way to reach a highly engaged + audience of potential customers. By sponsoring a stream, your + company can be featured prominently on the footer of the stream + of{" "} + + over 16K subscribers + + , with the opportunity to reach a large number of viewers who + are already interested in the topics your video is about. +

+
+
+
+ +
+
+
+

+ Why + + would you sponsor live coding? + +

+
+
    +
  • + Reach a large audience: + Live streaming on YouTube and Twitch is a great way to reach a + large audience. In fact, YouTube Live is one of the most popular + live streaming platforms in the world, with millions of viewers + tuning in each day. +
  • +
  • + Engage with your audience: + Live streaming is also a great way to engage with your audience. + You can interact with viewers in real time, answer questions, + and get feedback. This can help you build relationships with + your audience and create a sense of community. +
  • +
  • + Promote your brand: + Live streaming is a great way to promote your brand. You can use + live streaming to showcase your products or services, announce + new initiatives, or simply share your company culture. This can + help you raise awareness of your brand and attract new + customers. +
  • +
  • + Drive traffic to your website: + Live streaming can also help you drive traffic to your website. + You can include a link to your website in your live stream + description, or you can encourage viewers to visit your website + for more information. This can help you increase website traffic + and generate leads. +
  • +
+
+
+
+
+

+ If you're looking for a way to reach a large audience, engage + with your audience, promote your brand, and drive traffic to your + website, then sponsoring live YouTube is a great option. +

+
+
+ +
+
+ ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/page.tsx b/app/(main)/(top-level-pages)/sponsorships/page.tsx index 1b037d90..d710b737 100644 --- a/app/(main)/(top-level-pages)/sponsorships/page.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/page.tsx @@ -1,11 +1,11 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import { BreadcrumbLinks } from "@/components/breadrumb-links"; @@ -13,68 +13,75 @@ import CoverImage from "@/components/cover-image"; import SponsorshipCards from "./sponsorship-cards"; type Props = { - params: false; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params: { - slug: "sponsorships", - }, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "sponsorships", + }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function SponsorshipsPage() { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params: { - slug: "sponsorships", - }, - }), - ]); +export default async function SponsorshipsPage({ + params, + searchParams, +}: Props) { + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "sponsorships", + }, + stega: false, + }) + ).data as PageQueryResult; - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - return ( -
-
-
-

- {page.title} -

-
-
- {page?.coverImage && } + return ( +
+
+
+

+ {page.title} +

+
+
+ {page?.coverImage && } - -
-
- {page.content?.length && ( - - )} -
-
-
- ); + +
+
+ {page.content?.length && ( + + )} +
+
+
+ ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/podcast/page.tsx b/app/(main)/(top-level-pages)/sponsorships/podcast/page.tsx index df707a86..9e9a3137 100644 --- a/app/(main)/(top-level-pages)/sponsorships/podcast/page.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/podcast/page.tsx @@ -1,11 +1,11 @@ import type { Metadata, ResolvingMetadata } from "next"; -import { type PortableTextBlock } from "next-sanity"; +import type { PortableTextBlock } from "next-sanity"; import { notFound } from "next/navigation"; import PortableText from "@/components/portable-text"; -import type { PageQueryResult } from "@/sanity.types"; -import { sanityFetch } from "@/sanity/lib/fetch"; +import type { PageQueryResult } from "@/sanity/types"; +import { sanityFetch } from "@/sanity/lib/live"; import { pageQuery } from "@/sanity/lib/queries"; import { resolveOpenGraphImage } from "@/sanity/lib/utils"; import { BreadcrumbLinks } from "@/components/breadrumb-links"; @@ -16,497 +16,510 @@ import SponsorshipCards from "../sponsorship-cards"; import SponsorshipForm from "../sponsorship-form"; type Props = { - params: false; + params: Promise<{ slug: string }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; export async function generateMetadata( - { params }: Props, - parent: ResolvingMetadata + { params, searchParams }: Props, + parent: ResolvingMetadata, ): Promise { - const page = await sanityFetch({ - query: pageQuery, - params: { - slug: "podcast", - }, - stega: false, - }); - const previousImages = (await parent).openGraph?.images || []; - const ogImage = resolveOpenGraphImage(page?.coverImage); + const page = ( + await sanityFetch({ + query: pageQuery, + params: { + slug: "podcast", + }, + stega: false, + }) + ).data as PageQueryResult; + const previousImages = (await parent).openGraph?.images || []; + const ogImage = resolveOpenGraphImage(page?.coverImage); - return { - title: page?.title, - description: page?.excerpt, - openGraph: { - images: ogImage ? ogImage : previousImages, - }, - } satisfies Metadata; + return { + title: page?.title, + description: page?.excerpt, + openGraph: { + images: ogImage ? ogImage : previousImages, + }, + } satisfies Metadata; } -export default async function SponsorshipsPodcastPage() { - const [page] = await Promise.all([ - sanityFetch({ - query: pageQuery, - params: { - slug: "podcast", - }, - }), - ]); +export default async function SponsorshipsPodcastPage({ + params, + searchParams, +}: Props) { + const [page] = ( + await Promise.all([ + sanityFetch({ + query: pageQuery, + params: { + slug: "podcast", + }, + }), + ]) + ).map((res) => res.data as PageQueryResult); - if (!page?._id) { - return notFound(); - } + if (!page?._id) { + return notFound(); + } - const Arrow = () => ( - - - - ); + const Arrow = () => ( + + ); - return ( -
- -
-
-
-
- -
-
-

- Sponsorship for - - CodingCat.dev Podcast - - - a CodingCat.dev Production - -

-
- -
-
-
-
-
-

- On CodingCat.dev your advertisement is - - permanent - - ! -

-

- You read that right, it is not just while you are sponsoring - and it doesn't change by the flavor of the week like - Carbon or Google Ads. -

+ return ( +
+ +
+
+
+
+ +
+
+

+ Sponsorship for + + CodingCat.dev Podcast + + + a CodingCat.dev Production + +

+
+ +
+
+
+
+
+

+ On CodingCat.dev your advertisement is + + permanent + + ! +

+

+ You read that right, it is not just while you are sponsoring + and it doesn't change by the flavor of the week like + Carbon or Google Ads. +

-

- Podcast sponsorship is a great way to reach a highly engaged - audience of potential customers. By sponsoring a podcast, - your company can be featured prominently in the pre-roll and - mid-roll of a channel with{" "} - - over 16K subscribers - - , with the opportunity to reach a large number of viewers - who are already interested in the topics your video is - about. -

-
-
-
+

+ Podcast sponsorship is a great way to reach a highly engaged + audience of potential customers. By sponsoring a podcast, + your company can be featured prominently in the pre-roll and + mid-roll of a channel with{" "} + + over 16K subscribers + + , with the opportunity to reach a large number of viewers + who are already interested in the topics your video is + about. +

+
+
+
- + -
-
-

- Are you interested in reaching other web designers and - developers? -

-

- We‘d love to help! -

-

- CodingCat.dev Podcast is a weekly podcast that focuses on - developer‘s backgrounds, tools and tips. -

-

- We aim to keep listeners up to date on the latest technology - and best practices, along with guiding developers on their - journey and helping them use tools in their everyday workflow. -

-
-
-
-
-

- - {" "} - Why{" "} - - do we make the podcast? -

-
-
-
-

- - Alex - {" "} - created CodingCat.dev so that everyone has access to a great - learning platform and a safe learning community. He has a - primary background in web development and architecture. -

-
-
-
-
-
-

- - {" "} - Where{" "} - - - do we distribute the podcast? - -

-
-
-

- Our podcast is very visual and interactive, so we first - livestream to{" "} - - Twitch - {" "} - then the episodes receive a number for release and are - released to all the below syndication platforms. -

- -
-
-
-

- Audience Breakdown -

-
-
-
- Age Range -
-
- 25-34 -
-
- Most listeners fall within this range. -
-
-
-
Spotify
-
- -
-
-
-
YouTube
-
- -
-
-
-
-
-

- Sponsoring is Purrfect for: -

-
-
-
- -
-
- Web design and development tools, software and services -
-
- -
-
Teams looking to hire
-
- -
-
Technical training material and courses
-
- -
-
Technical software
-
- -
-
Hardware products
-
-
-
-
-

- Audience Interests: -

-
-
-

- Hard Skills -

-
-
- -
-
- JavaScript frameworks (e.g. React, Angular, Vue, and - Svelte) -
-
- -
-
CSS and CSS libraries like TailwindCSS
-
- -
-
Backend Frameworks (e.g. NodeJs, Rust)
-
- -
-
Cloud Solutions (e.g. AWS, GCP, Azure)
-
- -
-
- Lifestyle Products (e.g. keyboards, VSCode themes) -
-
-
-
-

- Soft Skills -

-
-
- -
-
How to get a job in tech
-
- -
-
How to run a freelance business
-
- -
-
How to start a podcast
-
- -
-
How to change careers
-
- -
-
Mental health and awareness
-
-
-
-
-
-
-

- - {" "} - Pricing{" "} - -

-
-
-
- - Single Show - - - $300 USD -
-
- - 3+ Shows - - - $250 USD -
-
- - 10+ Shows - - - $200 USD -
-

- * per show pricing, contact us to arrange for annual terms. -

-
-

- We have found that we get the best results for our advertisers - when they sponsor at least three shows, Alex and Brittney are - able to test out the product, and your marketing team approves - both pre-roll and mid-roll videos. -

-
-
-
-

- As part of the sponsorship package, you‘ll receive: -

-
- 1 -

- A sponsorship section within the episode show notes, on our - website. -

-
-

- These notes will be listed on CodingCat.dev Podcast - permanently and within the user‘s podcatcher of choice - (Apple, Spotify...). This is a great opportunity to include - unique targeted links and promo codes! -

-
-
- -
-
-
-
-
- 2 -

- A call-out in the pre-roll of the show. -

-
-

- The call-out will include the name of the company and slogan. - Because we are a video podcast, there will also be an - opportunity for your own branding to be included in the video. - We highly suggest your marketing team creates the video with a - voice-over from Brittney and Alex. -

-
-
-
- 3 -

- A 60-90 second sponsor spot mid-roll during the show. -

-
-

- We can provide a standard ad read provided by your marketing - department. We have found that because we are a video podcast, - this is a good time to showcase your product. We can also - provide a personal experience aad that allows Alex and - Brittney to demonstrate their own experience with your - product. -

-
-
-
-
-
- 4 -

- An evergreen listing on the CodingCat.dev Podcast sponsors - page. -

-
-

- This is a useful resource for listeners wanting to quickly - reference a sponsor‘s offering, but are unable to recall - which episode, coupon code, or link was used during the ad - read. -

-
-
-
- 5 -

- Access to a password protected dashboard. -

-
-

- This will include easy access to all documents, including - invoices and contracts. -

-
-
+
+
+

+ Are you interested in reaching other web designers and + developers? +

+

+ We‘d love to help! +

+

+ CodingCat.dev Podcast is a weekly podcast that focuses on + developer‘s backgrounds, tools and tips. +

+

+ We aim to keep listeners up to date on the latest technology + and best practices, along with guiding developers on their + journey and helping them use tools in their everyday workflow. +

+
+
+
+
+

+ + {" "} + Why{" "} + + do we make the podcast? +

+
+
+
+

+ + Alex + {" "} + created CodingCat.dev so that everyone has access to a great + learning platform and a safe learning community. He has a + primary background in web development and architecture. +

+
+
+
+
+
+

+ + {" "} + Where{" "} + + + do we distribute the podcast? + +

+
+
+

+ Our podcast is very visual and interactive, so we first + livestream to{" "} + + Twitch + {" "} + then the episodes receive a number for release and are + released to all the below syndication platforms. +

+ +
+
+
+

+ Audience Breakdown +

+
+
+
+ Age Range +
+
+ 25-34 +
+
+ Most listeners fall within this range. +
+
+
+
Spotify
+
+ +
+
+
+
YouTube
+
+ +
+
+
+
+
+

+ Sponsoring is Purrfect for: +

+
+
+
+ +
+
+ Web design and development tools, software and services +
+
+ +
+
Teams looking to hire
+
+ +
+
Technical training material and courses
+
+ +
+
Technical software
+
+ +
+
Hardware products
+
+
+
+
+

+ Audience Interests: +

+
+
+

+ Hard Skills +

+
+
+ +
+
+ JavaScript frameworks (e.g. React, Angular, Vue, and + Svelte) +
+
+ +
+
CSS and CSS libraries like TailwindCSS
+
+ +
+
Backend Frameworks (e.g. NodeJs, Rust)
+
+ +
+
Cloud Solutions (e.g. AWS, GCP, Azure)
+
+ +
+
+ Lifestyle Products (e.g. keyboards, VSCode themes) +
+
+
+
+

+ Soft Skills +

+
+
+ +
+
How to get a job in tech
+
+ +
+
How to run a freelance business
+
+ +
+
How to start a podcast
+
+ +
+
How to change careers
+
+ +
+
Mental health and awareness
+
+
+
+
+
+
+

+ + {" "} + Pricing{" "} + +

+
+
+
+ + Single Show + + - $300 USD +
+
+ + 3+ Shows + + - $250 USD +
+
+ + 10+ Shows + + - $200 USD +
+

+ * per show pricing, contact us to arrange for annual terms. +

+
+

+ We have found that we get the best results for our advertisers + when they sponsor at least three shows, Alex and Brittney are + able to test out the product, and your marketing team approves + both pre-roll and mid-roll videos. +

+
+
+
+

+ As part of the sponsorship package, you‘ll receive: +

+
+ 1 +

+ A sponsorship section within the episode show notes, on our + website. +

+
+

+ These notes will be listed on CodingCat.dev Podcast + permanently and within the user‘s podcatcher of choice + (Apple, Spotify...). This is a great opportunity to include + unique targeted links and promo codes! +

+
+
+ +
+
+
+
+
+ 2 +

+ A call-out in the pre-roll of the show. +

+
+

+ The call-out will include the name of the company and slogan. + Because we are a video podcast, there will also be an + opportunity for your own branding to be included in the video. + We highly suggest your marketing team creates the video with a + voice-over from Brittney and Alex. +

+
+
+
+ 3 +

+ A 60-90 second sponsor spot mid-roll during the show. +

+
+

+ We can provide a standard ad read provided by your marketing + department. We have found that because we are a video podcast, + this is a good time to showcase your product. We can also + provide a personal experience aad that allows Alex and + Brittney to demonstrate their own experience with your + product. +

+
+
+
+
+
+ 4 +

+ An evergreen listing on the CodingCat.dev Podcast sponsors + page. +

+
+

+ This is a useful resource for listeners wanting to quickly + reference a sponsor‘s offering, but are unable to recall + which episode, coupon code, or link was used during the ad + read. +

+
+
+
+ 5 +

+ Access to a password protected dashboard. +

+
+

+ This will include easy access to all documents, including + invoices and contracts. +

+
+
- - -
-
-
- {page.content?.length && ( - - )} -
-
-
- ); + + + + +
+ {page.content?.length && ( + + )} +
+ + + ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/podcast/podcatchers.tsx b/app/(main)/(top-level-pages)/sponsorships/podcast/podcatchers.tsx index 68590031..1c6c54f7 100644 --- a/app/(main)/(top-level-pages)/sponsorships/podcast/podcatchers.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/podcast/podcatchers.tsx @@ -1,177 +1,177 @@ import CoverImage from "@/components/cover-image"; export default function Podcatchers() { - return ( -
-
-
- - - + return ( +
+
+ + + + +
- -
- ); + + + +
+
+
+ ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/sponsorship-cards.tsx b/app/(main)/(top-level-pages)/sponsorships/sponsorship-cards.tsx index c8765b32..ee255072 100644 --- a/app/(main)/(top-level-pages)/sponsorships/sponsorship-cards.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/sponsorship-cards.tsx @@ -1,79 +1,80 @@ import CoverImage from "@/components/cover-image"; +import Link from "next/link"; export default function SponsorshipCards() { - return ( -
-

Sponsorships

-
-
- -
- -
-
-

Podcast Sponsorship

-
-

- Become an exclusive sponsor for a single episode, or multiple - podcasts. Includes both video and audio within the show. -

-
-
-
- -
- -
-
-

Code with CodingCat.dev Sponsorship

-
-

- Looking to showcase your product, live coding helps others to - explain how they can see your product! -

-
-
-
+ return ( +
+

Sponsorships

+
+
+ +
+ +
+
+

Podcast Sponsorship

+
+

+ Become an exclusive sponsor for a single episode, or multiple + podcasts. Includes both video and audio within the show. +

+
+
+ + +
+ +
+
+

Code with CodingCat.dev Sponsorship

+
+

+ Looking to showcase your product, live coding helps others to + explain how they can see your product! +

+
+
+ - -
- -
-
-

Blog Sponsorship

-
-

- Blog sponsorship is a great way to reach a highly engaged - audience of potential customers. -

-
-
-
-
-
- ); + +
+ +
+
+

Blog Sponsorship

+
+

+ Blog sponsorship is a great way to reach a highly engaged + audience of potential customers. +

+
+
+ +
+
+ ); } diff --git a/app/(main)/(top-level-pages)/sponsorships/sponsorship-form.tsx b/app/(main)/(top-level-pages)/sponsorships/sponsorship-form.tsx index 516ec37d..9789ec9a 100644 --- a/app/(main)/(top-level-pages)/sponsorships/sponsorship-form.tsx +++ b/app/(main)/(top-level-pages)/sponsorships/sponsorship-form.tsx @@ -4,61 +4,61 @@ import Script from "next/script"; import { Button } from "@/components/ui/button"; export default function SponsorshipForm() { - return ( - <> -