diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts index 5a486f4c04..1fa8020d86 100644 --- a/apps/docs/next.config.ts +++ b/apps/docs/next.config.ts @@ -176,7 +176,7 @@ const nextConfig: NextConfig = { // https://github.com/vercel/next.js/issues/71638 sassOptions: { logger: { - warn: function (message) { + warn: function (message: string) { if ( message.includes("deprecat") || message.includes("declarations that appear after nested") diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts index f36e80dd45..21c5ac5bcc 100644 --- a/apps/docs/source.config.ts +++ b/apps/docs/source.config.ts @@ -16,6 +16,8 @@ const schema = z.custom<{ date?: string; difficulty?: string; index?: boolean; + featured?: boolean; + href?: string; }>(); const docsData = defineDocs({ diff --git a/apps/docs/src/app/[locale]/developers/cookbook/[...slug]/page.tsx b/apps/docs/src/app/[locale]/developers/cookbook/[...slug]/page.tsx index 0ccc4efeed..3d77bfbdc8 100644 --- a/apps/docs/src/app/[locale]/developers/cookbook/[...slug]/page.tsx +++ b/apps/docs/src/app/[locale]/developers/cookbook/[...slug]/page.tsx @@ -3,7 +3,7 @@ import { CookbookPage, getMetadataFromSlug } from "../cookbook"; import { toStaticParams } from "@@/src/app/sources/utils"; type Props = { - params: Promise<{ slug?: string[]; locale: string }>; + params: Promise<{ slug: string[]; locale: string }>; }; export default async function Page(props: Props) { diff --git a/apps/docs/src/app/[locale]/developers/developers.tsx b/apps/docs/src/app/[locale]/developers/developers.tsx index 535288a74a..37f1052e4b 100644 --- a/apps/docs/src/app/[locale]/developers/developers.tsx +++ b/apps/docs/src/app/[locale]/developers/developers.tsx @@ -2,14 +2,23 @@ import DevelopersHeroSection from "@/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection"; import DevelopersCoursesSection from "@/components/developers/sections/DevelopersCoursesSection/DevelopersCoursesSection"; -import DevelopersResourcesSection from "@/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection"; +import DevelopersResourcesSection, { + DevelopersResourcesSectionProps, +} from "@/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection"; import DevelopersDocumentsSection from "@/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection"; import DevelopersContentSection from "@/components/developers/sections/DevelopersContentSection/DevelopersContentSection"; import heroImg from "@@/assets/developers/hero-geometry.png"; import { useTranslations } from "next-intl"; import StackExchangeIcon from "@@/assets/developers/stackexchange.inline.svg"; +import { LatestChangelogVideo } from "@/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog"; -export function DevelopersPage({ latestChangelogVideo, guides }) { +export function DevelopersPage({ + latestChangelogVideo, + guides, +}: { + latestChangelogVideo?: LatestChangelogVideo; + guides?: DevelopersResourcesSectionProps["items"]; +}) { const t = useTranslations(); return (
@@ -36,7 +45,7 @@ export function DevelopersPage({ latestChangelogVideo, guides }) { /> diff --git a/apps/docs/src/app/[locale]/developers/guides/guides.jsx b/apps/docs/src/app/[locale]/developers/guides/guides.tsx similarity index 80% rename from apps/docs/src/app/[locale]/developers/guides/guides.jsx rename to apps/docs/src/app/[locale]/developers/guides/guides.tsx index f25dadb242..ec5522af28 100644 --- a/apps/docs/src/app/[locale]/developers/guides/guides.jsx +++ b/apps/docs/src/app/[locale]/developers/guides/guides.tsx @@ -1,5 +1,6 @@ "use client"; +import type { ReactNode } from "react"; import Button from "@/components/shared/Button"; import RoundedDepthCard from "@/components/shared/RoundedDepthCard"; import DevelopersResources from "@/components/developers/DevelopersResources/DevelopersResources"; @@ -8,7 +9,21 @@ import { useTranslations } from "next-intl"; import classNames from "classnames"; import styles from "@/components/developers/DevelopersContentPage/DevelopersContentPage.module.scss"; -export function GuidesIndex({ records, featured }) { +export type GuideRecord = { + href?: string; + title?: ReactNode; + description?: ReactNode; + difficulty?: string; + tags?: string[]; + featured?: boolean; +}; + +type GuidesIndexProps = { + records: GuideRecord[]; + featured: GuideRecord[]; +}; + +export function GuidesIndex({ records, featured }: GuidesIndexProps) { const t = useTranslations(); return ( diff --git a/apps/docs/src/app/[locale]/developers/guides/page.tsx b/apps/docs/src/app/[locale]/developers/guides/page.tsx index 7f24276b4c..6be165684c 100644 --- a/apps/docs/src/app/[locale]/developers/guides/page.tsx +++ b/apps/docs/src/app/[locale]/developers/guides/page.tsx @@ -9,7 +9,7 @@ type Props = { export default async function Page(props: Props) { const { locale } = await props.params; const guides = getGuides(locale); - const featured = guides.filter((guide: any) => guide.featured).slice(0, 3); + const featured = guides.filter((guide) => guide.featured).slice(0, 3); return (
diff --git a/apps/docs/src/app/[locale]/developers/page.tsx b/apps/docs/src/app/[locale]/developers/page.tsx index 750403cdb5..70b69d847e 100644 --- a/apps/docs/src/app/[locale]/developers/page.tsx +++ b/apps/docs/src/app/[locale]/developers/page.tsx @@ -12,12 +12,12 @@ export default async function Page(props: Props) { const { locale } = await props.params; const latestChangelogVideo = await getLatestChangelogVideo(); const guides = getGuides(locale) - .filter((guide: any) => guide.featured) + .filter((guide) => guide.featured) .slice(0, 6); return ( ); } @@ -27,7 +27,8 @@ async function getLatestChangelogVideo() { let latestChangelogVideo = null; const videos = await getYTVideos(undefined, YT_PLAYLIST_CHANGELOG); if (videos.length) { - latestChangelogVideo = videos.sort((a: any, b: any) => { + latestChangelogVideo = videos.sort((a, b) => { + if (!b.snippet.publishedAt || !a.snippet.publishedAt) return 0; return ( new Date(b.snippet.publishedAt).getTime() - new Date(a.snippet.publishedAt).getTime() diff --git a/apps/docs/src/app/[locale]/docs/(main)/[...slug]/page.tsx b/apps/docs/src/app/[locale]/docs/(main)/[...slug]/page.tsx index 744af2b74c..c20474a07e 100644 --- a/apps/docs/src/app/[locale]/docs/(main)/[...slug]/page.tsx +++ b/apps/docs/src/app/[locale]/docs/(main)/[...slug]/page.tsx @@ -3,7 +3,7 @@ import { getMetadataFromSlug, MainDocsPage } from "../docs"; import { toStaticParams } from "@@/src/app/sources/utils"; type Props = { - params: Promise<{ slug?: string[]; locale: string }>; + params: Promise<{ slug: string[]; locale: string }>; }; export default async function Page(props: Props) { diff --git a/apps/docs/src/app/[locale]/docs/payments/[...slug]/page.tsx b/apps/docs/src/app/[locale]/docs/payments/[...slug]/page.tsx index 768d6bf1cd..7276ad790b 100644 --- a/apps/docs/src/app/[locale]/docs/payments/[...slug]/page.tsx +++ b/apps/docs/src/app/[locale]/docs/payments/[...slug]/page.tsx @@ -3,7 +3,7 @@ import { getMetadataFromSlug, PaymentsDocsPage } from "../payments"; import { toStaticParams } from "@@/src/app/sources/utils"; type Props = { - params: Promise<{ slug?: string[]; locale: string }>; + params: Promise<{ slug: string[]; locale: string }>; }; export default async function Page(props: Props) { diff --git a/apps/docs/src/app/[locale]/docs/rpc/[...slug]/page.tsx b/apps/docs/src/app/[locale]/docs/rpc/[...slug]/page.tsx index d17f587539..b66dd34a45 100644 --- a/apps/docs/src/app/[locale]/docs/rpc/[...slug]/page.tsx +++ b/apps/docs/src/app/[locale]/docs/rpc/[...slug]/page.tsx @@ -3,7 +3,7 @@ import { getMetadataFromSlug, RpcDocsPage } from "../rpc"; import { toStaticParams } from "@@/src/app/sources/utils"; type Props = { - params: Promise<{ slug?: string[]; locale: string }>; + params: Promise<{ slug: string[]; locale: string }>; }; export default async function Page(props: Props) { diff --git a/apps/docs/src/app/components/api-method.tsx b/apps/docs/src/app/components/api-method.tsx index ade64b8aa7..5942fb90ce 100644 --- a/apps/docs/src/app/components/api-method.tsx +++ b/apps/docs/src/app/components/api-method.tsx @@ -1,3 +1,4 @@ +import { isValidElement } from "react"; import { z } from "zod"; import { parseProps, Block, CodeBlock } from "codehike/blocks"; import { SingleCode, toCodeGroup } from "@@/src/app/components/code/code"; @@ -40,17 +41,45 @@ import { ResizableHandle, } from "@@/src/app/components/ui/resizable"; +type ParamBlock = { + title?: string; + children?: React.ReactNode; + type: string; + required: boolean; + values?: string[]; + default?: string; + blocks?: ParamBlock[]; +}; + +type Result = ParamBlock & { + response?: RawCode; +}; + +type Method = { + title?: string; + children?: React.ReactNode; + request: RawCode[]; + params: { + title?: string; + children?: React.ReactNode; + blocks?: ParamBlock[]; + }; + result: Result[]; +}; + const BaseParamSchema = Block.extend({ type: z.string(), required: z.optional(z.string()).transform((val) => val != null), values: z.optional(z.string()).transform((val) => val?.split(/\s+/)), default: z.string().optional(), }); -const ParamSchema = BaseParamSchema.extend({ + +const ParamSchema: z.ZodType = BaseParamSchema.extend({ blocks: z.lazy(() => ParamSchema.array()).optional(), }); -const ResultSchema = ParamSchema.extend({ +const ResultSchema: z.ZodType = BaseParamSchema.extend({ + blocks: z.lazy(() => ParamSchema.array()).optional(), response: CodeBlock.optional(), }); @@ -63,11 +92,7 @@ const MethodSchema = Block.extend({ }); export function APIMethod(props: unknown) { - const method = parseProps(props, MethodSchema) as { - params?: { blocks: ParamBlock[] }; - request: RawCode[]; - result: { response: RawCode; title: string }[]; - }; + const method = parseProps(props, MethodSchema) as Method; const paramsSection = ; const resultHeader = (
@@ -202,10 +227,7 @@ function SmallLayout({ ); } -type ParamBlock = z.infer & { - blocks?: ParamBlock[]; -}; -function ParamsSection({ params }: { params: ParamBlock[] }) { +function ParamsSection({ params }: { params?: ParamBlock[] }) { return ( <>

params

@@ -214,7 +236,7 @@ function ParamsSection({ params }: { params: ParamBlock[] }) { {params?.map((param, i) => (
@@ -241,9 +263,18 @@ function ParamsSection({ params }: { params: ParamBlock[] }) { } function ObjectParam({ block }: { block: ParamBlock }) { - const isEmpty = !(block.children as any)?.props.children; + const childElement = isValidElement<{ children?: React.ReactNode }>( + block.children, + ) + ? block.children + : null; + const isEmpty = !childElement?.props.children; return ( - + @@ -301,9 +333,6 @@ function ValuesTable({ block }: { block: ParamBlock }) { ); } -type Result = ParamBlock & { - response?: RawCode; -}; function ResultSection({ result }: { result: Result }) { return ( - a.name == "hover" && a.fromLineNumber === a.toLineNumber, + (a): a is BlockAnnotation => + a.name == "hover" && + (a as BlockAnnotation).fromLineNumber === + (a as BlockAnnotation).toLineNumber, ) - .map((a: BlockAnnotation) => { + .map((a) => { let line = lines[a.fromLineNumber - 1]; // remove potential trailing commas line = line.trim().replace(/,$/, ""); @@ -472,7 +503,8 @@ async function RequestBlock({ codeblocks }: { codeblocks: RawCode[] }) { return ; } -async function ResponseBlock({ codeblock }: { codeblock: RawCode }) { +async function ResponseBlock({ codeblock }: { codeblock?: RawCode }) { + if (!codeblock) return null; const highlighted = await highlight(codeblock, theme); const handlers = [mark, tokenTransitions, hover, ...collapse]; const codeGroup = { diff --git a/apps/docs/src/app/components/blog-page.tsx b/apps/docs/src/app/components/blog-page.tsx index 20ac54f1fc..bc62443219 100644 --- a/apps/docs/src/app/components/blog-page.tsx +++ b/apps/docs/src/app/components/blog-page.tsx @@ -25,12 +25,12 @@ export function BlogPage(props: { tags?: string[]; date?: string; difficulty?: string; - pageTree?: any; + pageTree?: Parameters[0]; }) { const record: ContentRecord = { href: props.href, title: props.title, - date: props.date, + date: props.date ?? "", tags: props.tags, difficulty: props.difficulty, }; @@ -42,7 +42,7 @@ export function BlogPage(props: { return (
- +
@@ -57,21 +57,29 @@ export function BlogPage(props: { - +
{props.children} -
+ {props.pageTree ? ( +
+ ) : null}
); } -function Footer({ pageTree, pageUrl }: { pageTree: any; pageUrl: string }) { +function Footer({ + pageTree, + pageUrl, +}: { + pageTree: Parameters[0]; + pageUrl: string; +}) { let { next, previous } = findNeighbour(pageTree, pageUrl); // only if neighbours are in the same folder diff --git a/apps/docs/src/app/components/breadcrumb.tsx b/apps/docs/src/app/components/breadcrumb.tsx index 261305e71a..2846e0d890 100644 --- a/apps/docs/src/app/components/breadcrumb.tsx +++ b/apps/docs/src/app/components/breadcrumb.tsx @@ -33,7 +33,7 @@ export function Breadcrumb({ className="truncate hover:text-fd-accent-foreground" href={`/${rootParts.slice(0, index + 1).join("/")}`} > - {roots[part]} + {roots[part as keyof typeof roots]} ))} diff --git a/apps/docs/src/app/components/code-reference.tsx b/apps/docs/src/app/components/code-reference.tsx index 0e008b44d7..d559170b75 100644 --- a/apps/docs/src/app/components/code-reference.tsx +++ b/apps/docs/src/app/components/code-reference.tsx @@ -1,6 +1,6 @@ import { z } from "zod"; import { parseProps, Block, CodeBlock } from "codehike/blocks"; -import { AnnotationHandler } from "codehike/code"; +import { AnnotationHandler, RawCode } from "codehike/code"; import { isValidElement, ReactNode } from "react"; import { cn } from "@@/src/app/components/utils"; import { Code } from "./code/code"; @@ -12,11 +12,30 @@ import { import { ChevronDown } from "lucide-react"; import { Hoverable, HoverBlock, HoverProvider } from "./code/hover.client"; +type FieldBlock = { + title?: string; + children?: ReactNode; + type: string; + blocks?: FieldBlock[]; +}; + +type Reference = { + title?: string; + children?: ReactNode; + code: RawCode[]; + reference: { + title?: string; + children?: ReactNode; + blocks?: FieldBlock[]; + }; +}; + const BaseFieldSchema = Block.extend({ type: z.string(), }); -const FieldSchema = BaseFieldSchema.extend({ - blocks: z.lazy(() => FieldSchema.array()).optional(), + +const FieldSchema: z.ZodType = BaseFieldSchema.extend({ + blocks: z.lazy(() => z.array(FieldSchema)).optional(), }); const Schema = Block.extend({ @@ -26,19 +45,13 @@ const Schema = Block.extend({ }), }); -type FieldBlock = z.infer & { - blocks?: FieldBlock[]; -}; - const hover: AnnotationHandler = { name: "hover", Block: HoverBlock, }; export async function CodeReference(props: unknown) { - const { code, reference } = parseProps(props, Schema) as z.infer< - typeof Schema - >; + const { code, reference } = parseProps(props, Schema) as Reference; return ( @@ -79,7 +92,8 @@ function FieldsSection({ return (
{fields.map((field, i) => { - const path = prefix ? `${prefix}.${field.title}` : field.title; + const title = field.title ?? ""; + const path = prefix ? `${prefix}.${title}` : title; return ( , + icon: , lang: tab.lang, pre: (
       {props.children}
     
diff --git a/apps/docs/src/app/components/code/mermaid-renderer.tsx b/apps/docs/src/app/components/code/mermaid-renderer.tsx
index 073ead4013..0579395991 100644
--- a/apps/docs/src/app/components/code/mermaid-renderer.tsx
+++ b/apps/docs/src/app/components/code/mermaid-renderer.tsx
@@ -5,7 +5,7 @@ import { useEffect, useId, useRef, useState, type RefObject } from "react";
 import type { MermaidConfig } from "mermaid";
 import { Loader2 } from "lucide-react";
 
-function useIsVisible(ref: RefObject) {
+function useIsVisible(ref: RefObject) {
   const [isIntersecting, setIsIntersecting] = useState(false);
 
   useEffect(() => {
@@ -40,7 +40,7 @@ export function MermaidRenderer({
   const id = useId();
   const [svg, setSvg] = useState("");
   const [isLoading, setIsLoading] = useState(true);
-  const containerRef = useRef(null);
+  const containerRef = useRef(null);
   const isVisible = useIsVisible(containerRef);
   const svgPanZoomInstance = useRef(null);
 
diff --git a/apps/docs/src/app/components/code/notes.tsx b/apps/docs/src/app/components/code/notes.tsx
index 47491c0ae5..c9cd2a5333 100644
--- a/apps/docs/src/app/components/code/notes.tsx
+++ b/apps/docs/src/app/components/code/notes.tsx
@@ -1,5 +1,6 @@
 import { Code } from "./code";
 import { WithClientNotes } from "./notes.client";
+import { RawCode } from "codehike/code";
 
 export function WithNotes({
   children,
@@ -9,28 +10,38 @@ export function WithNotes({
 }) {
   // get all the blocks inside 
   // and put them into Context
-  const notes = Object.entries(rest)
+  const notes = (
+    Object.entries(rest) as [
+      string,
+      (
+        | RawCode
+        | {
+            type?: "prose" | "code" | "image";
+            children?: React.ReactNode;
+            url?: string;
+            alt?: string;
+          }
+      ),
+    ][]
+  )
     .filter(([name]) => name !== "title" && name !== "_data")
-    .map(([name, block]: any) => {
-      if (block.hasOwnProperty("children")) {
+    .map(([name, block]) => {
+      if ("children" in block) {
         return {
           name,
-          type: block.type || "prose",
+          type: block.type || ("prose" as const),
           children: block.children,
         };
-      } else if (
-        block.hasOwnProperty("value") &&
-        block.hasOwnProperty("lang")
-      ) {
+      } else if ("value" in block && "lang" in block) {
         return {
           name,
-          type: "code",
+          type: "code" as const,
           children: ,
         };
-      } else if (block.hasOwnProperty("url") && block.hasOwnProperty("alt")) {
+      } else if ("url" in block && "alt" in block) {
         return {
           name,
-          type: "image",
+          type: "image" as const,
           children: {block.alt},
         };
       } else {
diff --git a/apps/docs/src/app/components/code/scrollycoding.client.tsx b/apps/docs/src/app/components/code/scrollycoding.client.tsx
index e4c61cc472..efb51d65e5 100644
--- a/apps/docs/src/app/components/code/scrollycoding.client.tsx
+++ b/apps/docs/src/app/components/code/scrollycoding.client.tsx
@@ -12,7 +12,7 @@ type StickerStep = {
 
 export function SelectionSticker({ steps }: { steps: StickerStep[] }) {
   const stickerSteps = useMemo(() => {
-    const files = {};
+    const files: Record = {};
     return steps.map((step) => {
       Object.entries(step.codeblocks).forEach(([title, code]) => {
         files[title] = code;
@@ -38,7 +38,9 @@ function Sticker(props: {
   const { codeblocks } = step;
   const filetree = useMemo(() => getFileTree(codeblocks), [codeblocks]);
   const defaultSelected = step.selected;
-  const [selected, setSelected] = useState(defaultSelected);
+  const [selected, setSelected] = useState(
+    defaultSelected ?? null,
+  );
 
   useEffect(() => {
     if (defaultSelected) {
@@ -114,7 +116,7 @@ function FileNodes(props: {
        onSelect(node.title)}
+        onClick={() => node.title && onSelect(node.title)}
         className={cn(
           "transition-colors duration-300",
           selected === node.title
diff --git a/apps/docs/src/app/components/code/scrollycoding.tsx b/apps/docs/src/app/components/code/scrollycoding.tsx
index 6dd91cf78c..652fa0676f 100644
--- a/apps/docs/src/app/components/code/scrollycoding.tsx
+++ b/apps/docs/src/app/components/code/scrollycoding.tsx
@@ -21,11 +21,15 @@ const Schema = Block.extend({
   ),
 });
 
-type Steps = z.infer["steps"];
+type Step = {
+  title?: string;
+  children?: React.ReactNode;
+  code: RawCode[];
+};
+type Steps = Step[];
 
-export function ScrollyCoding(props: unknown) {
-  // @ts-expect-error props are not typed
-  const { steps } = parseProps(props, Schema);
+export function ScrollyCoding(props: { steps: unknown[] }) {
+  const { steps } = parseProps(props, Schema) as { steps: Steps };
   return (
     
@@ -38,7 +42,7 @@ function OneColumnLayout(props: { steps: Steps; className?: string }) { const { steps, className } = props; return (
- {steps.map((step, i) => ( + {steps.map((step: Steps[number], i: number) => ( ))}
@@ -50,7 +54,7 @@ function OneColumnStep(props: { step: Steps[number] }) { const codeblocks = {} as Record; step.code.forEach((code) => { const { title } = extractFlags(code); - codeblocks[title] = ; + codeblocks[title ?? ""] = ; }); return ( @@ -123,8 +127,9 @@ function getStickers(steps: Steps) { step.code.forEach((code, i) => { const { title } = extractFlags(code); - codeMap[title] = code; - if (i === 0) selected = title; + const key = title ?? ""; + codeMap[key] = code; + if (i === 0) selected = key; }); return { codes: codeMap, selected }; diff --git a/apps/docs/src/app/components/docs-footer.tsx b/apps/docs/src/app/components/docs-footer.tsx index 93f820190c..044c025e5b 100644 --- a/apps/docs/src/app/components/docs-footer.tsx +++ b/apps/docs/src/app/components/docs-footer.tsx @@ -4,7 +4,8 @@ import { ChevronLeft, ChevronRight } from "lucide-react"; import { useI18n } from "fumadocs-ui/provider"; import { ReactNode } from "react"; -type DocsLink = { name: ReactNode; url: string }; +export type DocsLink = { name: ReactNode; url: string }; + export function DocsFooter({ previous, next, diff --git a/apps/docs/src/app/components/docs-layout.tsx b/apps/docs/src/app/components/docs-layout.tsx index 8e55a4a6b3..8f985d22ef 100644 --- a/apps/docs/src/app/components/docs-layout.tsx +++ b/apps/docs/src/app/components/docs-layout.tsx @@ -15,7 +15,7 @@ export async function DocsLayout({ children, tree, sidebarEnabled = true, - locale, + locale = "en", }: { children: ReactNode; tree: any; // TODO: fix after updating to fumadocs-ui@15 diff --git a/apps/docs/src/app/components/docs-page.tsx b/apps/docs/src/app/components/docs-page.tsx index 5fcbd0ad4b..9a07f49798 100644 --- a/apps/docs/src/app/components/docs-page.tsx +++ b/apps/docs/src/app/components/docs-page.tsx @@ -6,7 +6,7 @@ import { import { ReactNode } from "react"; import { ScrollToTop } from "./scroll-to-top"; import { EditOnGithub } from "./edit-page"; -import { DocsFooter } from "./docs-footer"; +import { DocsFooter, DocsLink } from "./docs-footer"; import { findNeighbour } from "fumadocs-core/server"; import { Rate } from "./rate"; import { onRateAction } from "./inkeep/inkeep-feedback"; @@ -22,7 +22,7 @@ export function DocsPage(props: { full?: boolean; title: string; description?: string; - pageTree?: any; + pageTree?: Parameters[0]; href: string; markdown: string; isRoot?: boolean; @@ -56,7 +56,9 @@ export function DocsPage(props: { enabled: !props.hideTableOfContents, }} footer={{ - component:
, + component: props.pageTree ? ( +
+ ) : undefined, }} > {props.hideHeader ? null : props.isRoot ? ( @@ -114,17 +116,23 @@ function getEditUrl(path: string, editPathPrefix = "content/docs") { return `https://github.com/solana-foundation/solana-com/blob/main/apps/docs/${editPathPrefix}/${path.startsWith("/") ? path.slice(1) : path}`; } -function Footer({ pageUrl, pageTree }: { pageUrl: string; pageTree: any }) { +function Footer({ + pageUrl, + pageTree, +}: { + pageUrl: string; + pageTree: Parameters[0]; +}) { const { next, previous } = findNeighbour(pageTree, pageUrl); if (!previous && !next) { // we are at the root (which isn't part of the page tree) - let firstPage = pageTree; + let firstPage = pageTree as any; while (firstPage && firstPage.children) { firstPage = firstPage.index || firstPage.children[0]; } if (!firstPage) return null; - return ; + return ; } return ; } diff --git a/apps/docs/src/app/components/rate.tsx b/apps/docs/src/app/components/rate.tsx index e5645d3147..3dac7e3f0c 100644 --- a/apps/docs/src/app/components/rate.tsx +++ b/apps/docs/src/app/components/rate.tsx @@ -56,7 +56,7 @@ export function Rate({ try { const stream = await onRateAction(pathname, { - opinion: opinion, + opinion: opinion!, message, }); diff --git a/apps/docs/src/app/mdx-components.tsx b/apps/docs/src/app/mdx-components.tsx index b6f6022455..f41374ed9f 100644 --- a/apps/docs/src/app/mdx-components.tsx +++ b/apps/docs/src/app/mdx-components.tsx @@ -68,7 +68,7 @@ function Link(props: LinkProps) { if (props.href?.startsWith("mention:")) { return ; } else if (props.href?.startsWith("tooltip")) { - return {props.children}; + return {props.children}; } return ; } diff --git a/apps/docs/src/app/metadata.ts b/apps/docs/src/app/metadata.ts index 60821fea05..0af7d40649 100644 --- a/apps/docs/src/app/metadata.ts +++ b/apps/docs/src/app/metadata.ts @@ -5,6 +5,7 @@ import { getTranslations } from "next-intl/server"; import faviconPng from "@solana-com/ui-chrome/assets/favicon.png"; import faviconSvg from "@solana-com/ui-chrome/assets/favicon.svg"; import appleTouchIcon from "@solana-com/ui-chrome/assets/apple-touch-icon.png"; +import { Page } from "fumadocs-core/source"; export function getBaseMetadata(locale: string) { const { siteMetadata, siteUrl } = config; @@ -54,6 +55,11 @@ export async function getIndexMetadata({ descriptionKey, locale, path, +}: { + titleKey: string; + descriptionKey: string; + locale: string; + path: string; }) { const t = await getTranslations(); return { @@ -63,11 +69,18 @@ export async function getIndexMetadata({ }; } -export function getMdxMetadata(page) { +type DocPage = Page<{ + title: string; + seoTitle?: string; + h1?: string; + description?: string; +}>; + +export function getMdxMetadata(page: DocPage) { const url = getUrlWithoutLocale(page); const title = page.data.seoTitle || page.data.h1 || page.data.title; const description = page.data.description; - const { openGraph } = getBaseMetadata(page.locale); + const { openGraph } = getBaseMetadata(page.locale ?? "en"); const imagePrefix = url?.startsWith("/docs") ? "/opengraph/developers" @@ -76,7 +89,7 @@ export function getMdxMetadata(page) { return { title, description, - alternates: getAlternates(url, page.locale), + alternates: getAlternates(url, page.locale ?? "en"), openGraph: { ...openGraph, images: [imagePrefix + url], diff --git a/apps/docs/src/app/opengraph/developers/[[...slug]]/route.ts b/apps/docs/src/app/opengraph/developers/[[...slug]]/route.ts index 1a68521e1f..72a7130d18 100644 --- a/apps/docs/src/app/opengraph/developers/[[...slug]]/route.ts +++ b/apps/docs/src/app/opengraph/developers/[[...slug]]/route.ts @@ -39,8 +39,11 @@ function getImageProps(slugItems: Array) { const [first, ...rest] = slugItems; const name = first.toLowerCase(); let title: string | null = null; - if (collections[name]) { - title = getTitleFromCollection(collections[name], rest); + if (collections[name as keyof typeof collections]) { + title = getTitleFromCollection( + collections[name as keyof typeof collections], + rest, + ); if (!title) { return null; } @@ -61,7 +64,7 @@ function getImageProps(slugItems: Array) { } function getTitleFromCollection( - collection: any, + collection: (typeof collections)[keyof typeof collections], slugs: Array, ): string | null { const page = collection.getPage(slugs); diff --git a/apps/docs/src/app/sources/guides.ts b/apps/docs/src/app/sources/guides.ts index ad903cf793..1bfe9302ff 100644 --- a/apps/docs/src/app/sources/guides.ts +++ b/apps/docs/src/app/sources/guides.ts @@ -2,6 +2,18 @@ import { guides, guidesMeta } from "@@/.source/index"; import { createMDXSource } from "fumadocs-mdx"; import { loader } from "fumadocs-core/source"; import { locales, defaultLocale } from "@workspace/i18n/config"; +import { ReactNode } from "react"; + +type GuideNode = Omit<(typeof guidesSource.pageTree)[string], "children"> & { + url?: string; + href?: string; + title?: ReactNode; + description?: ReactNode; + difficulty?: string; + tags?: string[]; + featured?: boolean; + children?: GuideNode[]; +}; export const guidesSource = loader({ i18n: { @@ -12,23 +24,36 @@ export const guidesSource = loader({ baseUrl: "/developers/guides", source: createMDXSource(guides, guidesMeta), pageTree: { - attachFile(node: any, file: any) { - node.description = file.data?.description; - node.slug = file.slugs?.join("/"); - node.featured = file.data?.featured; - node.tags = file.data?.tags; - node.href = file.data?.href; - node.difficulty = file.data?.difficulty; + attachFile(node, file) { + node.description = file?.data?.description; + // @ts-expect-error Adds an unspecified prop + node.slug = file?.slugs?.join("/"); + // @ts-expect-error Adds an unspecified prop + node.featured = file?.data?.featured; + // @ts-expect-error Adds an unspecified prop + node.tags = file?.data?.tags; + // @ts-expect-error Adds an unspecified prop + node.href = file?.data?.href; + // @ts-expect-error Adds an unspecified prop + node.difficulty = file?.data?.difficulty; return node; }, }, }); export function getGuides(locale: string) { - return getGuidesFromFolder(guidesSource.pageTree[locale]); + return getGuidesFromFolder(guidesSource.pageTree[locale] as GuideNode); } -function getGuidesFromFolder(folder: { children: any[] }) { +function getGuidesFromFolder(folder: GuideNode): { + href?: string; + title?: ReactNode; + description?: ReactNode; + difficulty?: string; + tags?: string[]; + featured?: boolean; +}[] { + if (!folder.children) return []; return folder.children.flatMap((node) => { if (node.children) { return getGuidesFromFolder(node); diff --git a/apps/docs/src/app/sources/utils.ts b/apps/docs/src/app/sources/utils.ts index d6224bbb8e..d3509a716c 100644 --- a/apps/docs/src/app/sources/utils.ts +++ b/apps/docs/src/app/sources/utils.ts @@ -1,15 +1,13 @@ import { defaultLocale } from "@workspace/i18n/config"; -import { LoaderConfig, LoaderOutput, Page } from "fumadocs-core/source"; +import { LoaderConfig, LoaderOutput } from "fumadocs-core/source"; // remove extra params generated by fumadocs i18n export function toStaticParams(source: LoaderOutput) { return source.generateParams().filter(({ lang }) => lang === defaultLocale); } -export function getUrlWithoutLocale( - page: Page, -) { - return toUrlWithoutLocale(page.url, page.locale); +export function getUrlWithoutLocale(page: { url: string; locale?: string }) { + return toUrlWithoutLocale(page.url, page.locale ?? "en"); } export function toUrlWithoutLocale(url: string, locale: string) { diff --git a/apps/docs/src/components/CookieConsent/CookieConsent.jsx b/apps/docs/src/components/CookieConsent/CookieConsent.tsx similarity index 100% rename from apps/docs/src/components/CookieConsent/CookieConsent.jsx rename to apps/docs/src/components/CookieConsent/CookieConsent.tsx diff --git a/apps/docs/src/components/GTMTrackingSnippet.js b/apps/docs/src/components/GTMTrackingSnippet.tsx similarity index 100% rename from apps/docs/src/components/GTMTrackingSnippet.js rename to apps/docs/src/components/GTMTrackingSnippet.tsx diff --git a/apps/docs/src/components/SolFormattedMessage/index.js b/apps/docs/src/components/SolFormattedMessage/index.tsx similarity index 64% rename from apps/docs/src/components/SolFormattedMessage/index.js rename to apps/docs/src/components/SolFormattedMessage/index.tsx index dfc9d56d8f..faff34d275 100644 --- a/apps/docs/src/components/SolFormattedMessage/index.js +++ b/apps/docs/src/components/SolFormattedMessage/index.tsx @@ -1,16 +1,20 @@ import { useRouter } from "@workspace/i18n/use-router"; import { useEffect, useMemo, useState } from "react"; -export const formatNumber = (locale, value, options) => { +export const formatNumber = ( + locale: string | string[] | undefined, + value: number | bigint | string, + options?: Intl.NumberFormatOptions, +): string | number | bigint => { try { - return Intl.NumberFormat(locale, options).format(value); + return Intl.NumberFormat(locale, options).format(value as number); } catch (error) { console.error(error); return value; } }; -const humanFriendlyNumbers = { +const humanFriendlyNumbers: Record = { 1: "", 1e3: "K", 1e6: "M", @@ -22,22 +26,15 @@ const humanFriendlyNumbers = { /** * Format a number down and adds the above extensions. - * - * @param {string} locale - * @param {number} value - * @param {number} digits How many digits to display after the floating point, defaults to 1. - * @param {number} startDividing When to start dividing, default to 1e4, divides by 1e3. - * @param {NumberFormatOptions} options - * @return {`${*}${*}`|`${*}${*}`} */ export const formatNumberHumanFriendly = ( - locale, - value, - digits = 1, - startDividing = 1e4, - options = {}, -) => { - let dividend; + locale: string | string[] | undefined, + value: number, + digits: number = 1, + startDividing: number = 1e4, + options: Intl.NumberFormatOptions = {}, +): string => { + let dividend: number; switch (true) { case value >= 1e18: { dividend = 1e18; @@ -67,13 +64,16 @@ export const formatNumberHumanFriendly = ( dividend = 1; } const dividedDown = (value / dividend).toFixed(digits); - // console.log(value, dividedDown, dividend); return `${formatNumber(locale, dividedDown, options)}${ humanFriendlyNumbers[dividend] }`; }; -export function FormattedNumber({ value, ...options }) { +type FormattedNumberProps = Intl.NumberFormatOptions & { + value: number; +}; + +export function FormattedNumber({ value, ...options }: FormattedNumberProps) { const { locale } = useRouter(); const formatted = useMemo(() => { @@ -88,9 +88,13 @@ export function FormattedNumber({ value, ...options }) { return <>{formatted}; } -export function FormattedDate({ value, ...options }) { +type FormattedDateProps = Intl.DateTimeFormatOptions & { + value: number | Date; +}; + +export function FormattedDate({ value, ...options }: FormattedDateProps) { const { locale } = useRouter(); - const [date, setDate] = useState(null); + const [date, setDate] = useState(null); useEffect(() => { setDate(value); @@ -109,5 +113,5 @@ export function FormattedDate({ value, ...options }) { } }, [locale, date, options]); - return <>{formatted}; + return <>{formatted as React.ReactNode}; } diff --git a/apps/docs/src/components/developers/DevelopersContentPage/DevelopersContentPage.tsx b/apps/docs/src/components/developers/DevelopersContentPage/DevelopersContentPage.tsx index 8f1fa53218..2de1e79bee 100644 --- a/apps/docs/src/components/developers/DevelopersContentPage/DevelopersContentPage.tsx +++ b/apps/docs/src/components/developers/DevelopersContentPage/DevelopersContentPage.tsx @@ -62,7 +62,7 @@ export function HeroTitle({ )} */}

- +
@@ -70,8 +70,9 @@ export function HeroTitle({ {record.title} > - + {item?.label || item?.title} diff --git a/apps/docs/src/components/developers/DevelopersContentPage/PageNav.tsx b/apps/docs/src/components/developers/DevelopersContentPage/PageNav.tsx index aa50584c41..e7a259bb92 100644 --- a/apps/docs/src/components/developers/DevelopersContentPage/PageNav.tsx +++ b/apps/docs/src/components/developers/DevelopersContentPage/PageNav.tsx @@ -3,7 +3,19 @@ import styles from "./DevelopersContentPage.module.scss"; import Link from "next/link"; import { useTranslations } from "next-intl"; -export const PageNav = ({ nav }) => { +type NavLink = { + href: string; + label: string; +}; + +type PageNavProps = { + nav: { + prev?: NavLink | null; + next?: NavLink | null; + }; +}; + +export const PageNav = ({ nav }: PageNavProps) => { const t = useTranslations(); return ( diff --git a/apps/docs/src/components/developers/DevelopersContentPage/TagCloud.tsx b/apps/docs/src/components/developers/DevelopersContentPage/TagCloud.tsx index 1b48d07892..99a4f83c37 100644 --- a/apps/docs/src/components/developers/DevelopersContentPage/TagCloud.tsx +++ b/apps/docs/src/components/developers/DevelopersContentPage/TagCloud.tsx @@ -2,11 +2,20 @@ import { memo } from "react"; import styles from "./DevelopersContentPage.module.scss"; import Link from "next/link"; +export type TagCloudRecord = { + title: string; + href: string; + date: string; + tags?: string[]; + difficulty?: string; + category?: string; +}; + type TagCloudProps = { /** base href to compute the correct tag route */ baseHref: string; /** content record used */ - record: any; + record: TagCloudRecord | null | undefined; }; /** diff --git a/apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.jsx b/apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.tsx similarity index 77% rename from apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.jsx rename to apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.tsx index fdde73601f..8e95752183 100644 --- a/apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.jsx +++ b/apps/docs/src/components/developers/DevelopersFeaturedResourcesList/DevelopersFeaturedResourcesList.tsx @@ -1,13 +1,30 @@ -import { memo } from "react"; +import { memo, type ReactNode } from "react"; import classNames from "classnames"; import { useTranslations } from "next-intl"; import styles from "./DevelopersFeaturedResourcesList.module.scss"; import DevelopersFeaturedResourcesListItem from "./DevelopersFeaturedResourcesListItem/DevelopersFeaturedResourcesListItem"; +type FeaturedResourceItem = { + title?: ReactNode; + description?: ReactNode; + href?: string; + url?: string; + isExternal?: boolean; + data?: { + title?: string; + description?: string; + }; +}; + +type DevelopersFeaturedResourcesListProps = { + items: FeaturedResourceItem[]; + translationTag?: string; +}; + export default memo(function DevelopersFeaturedResourcesList({ items, translationTag = "featured-resources-list", -}) { +}: DevelopersFeaturedResourcesListProps) { const t = useTranslations(); return (
@@ -32,10 +49,15 @@ const Content = memo(function Content({ ); }); +type DevelopersFeaturedResourcesListItemProps = { + item: FeaturedResourceItem; + translationTag?: string; +}; + export default memo(function DevelopersFeaturedResourcesListItem({ item, translationTag, -}) { +}: DevelopersFeaturedResourcesListItemProps) { if (item.isExternal) { return ( import("../DevelopersResourcesFilters/DevelopersResourcesFilters"), @@ -12,45 +12,48 @@ const DevelopersResourcesFilters = dynamic( }, ); -const mapItemsIntoFilters = (itemFilters) => (item) => { - // handle the `category` field - if (!!item?.category) { - if (!itemFilters.category.items.includes(item?.category)) { - itemFilters.category.items.push(item?.category); - } - } +type ResourceItem = { + category?: string; + difficulty?: string; + labels?: string[]; + tags?: string[]; + [key: string]: unknown; +}; - // handle the `difficulty` field - if (!!item?.difficulty) { - if (!itemFilters.difficulty.items.includes(item?.difficulty)) { - // console.log(item.difficulty); - itemFilters.difficulty.items.push(item?.difficulty); +const mapItemsIntoFilters = + (itemFilters: FilterMap) => (item: ResourceItem) => { + if (item?.category) { + if (!itemFilters.category.items.includes(item.category)) { + itemFilters.category.items.push(item.category); + } } - } - // handle the `labels` field - if (!!item?.labels) { - item.labels?.forEach((label) => { - if (!itemFilters?.labels.items.includes(label)) { - itemFilters.labels.items.push(label); + if (item?.difficulty) { + if (!itemFilters.difficulty.items.includes(item.difficulty)) { + itemFilters.difficulty.items.push(item.difficulty); } - }); - } + } - // handle the `tags` field - if (!!item?.tags) { - item.tags.forEach((tag) => { - if (!itemFilters.tags.items.includes(tag)) { - itemFilters.tags.items.push(tag); - } - }); - } + if (item?.labels) { + item.labels?.forEach((label) => { + if (!itemFilters?.labels.items.includes(label)) { + itemFilters.labels.items.push(label); + } + }); + } - return item; -}; + if (item?.tags) { + item.tags.forEach((tag) => { + if (!itemFilters.tags.items.includes(tag)) { + itemFilters.tags.items.push(tag); + } + }); + } + + return item; + }; -// define the base filters to be used -const baseFilters = { +const baseFilters: FilterMap = { difficulty: { label: "Difficulty", items: [], @@ -69,16 +72,20 @@ const baseFilters = { }, }; -export default memo(function DevelopersResources({ items, title = "" }) { - // populate and memoize the available filters, +type DevelopersResourcesProps = { + items: ResourceItem[]; + title?: string; +}; + +export default memo(function DevelopersResources({ + items, + title = "", +}: DevelopersResourcesProps) { const filters = useMemo(() => { - // reset the filters to the their base const filters = baseFilters; - // map all the filterable items to populate the filters items.map(mapItemsIntoFilters(filters)); - // deduplicate all filters filters.difficulty.items = Array.from( new Set(filters.difficulty.items.map((item) => item.toLowerCase())), ); @@ -92,7 +99,6 @@ export default memo(function DevelopersResources({ items, title = "" }) { new Set(filters.labels.items.map((item) => item.toLowerCase())), ); - // force sort all filters to the same, always for (const key in filters) filters[key].items.sort(); return filters; diff --git a/apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.jsx b/apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.tsx similarity index 76% rename from apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.jsx rename to apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.tsx index 3b015466dd..81cad52ce0 100644 --- a/apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.jsx +++ b/apps/docs/src/components/developers/DevelopersResourcesFilters/DevelopersResourcesFilters.tsx @@ -5,7 +5,26 @@ import { useRouter, useSearchParams } from "next/navigation"; import classNames from "classnames"; import styles from "./DevelopersResourcesFilters.module.scss"; -function Filters({ filters, onReset, onToggle, activeFilters = new Map() }) { +export type FilterGroup = { + label: string; + items: string[]; +}; + +export type FilterMap = Record; + +type FiltersProps = { + filters: FilterMap; + onReset: () => void; + onToggle: (_key: string, _filter: string) => void; + activeFilters?: Map; +}; + +function Filters({ + filters, + onReset, + onToggle, + activeFilters = new Map(), +}: FiltersProps) { return (
Filters
@@ -27,7 +46,7 @@ function Filters({ filters, onReset, onToggle, activeFilters = new Map() }) {
{filters[key].items.map((filter) => - !!filter ? ( + filter ? (
{ + const createQueryString = useCallback((params: Record) => { const urlSearchParams = new URLSearchParams(); Object.entries(params).forEach(([key, values]) => { if (Array.isArray(values)) { @@ -78,21 +97,19 @@ function FilterLogic({ filters }) { }, [router]); const toggleFilter = useCallback( - (key, filter) => { + (key: string, filter: string) => { const currentValues = searchParams.getAll(key); const newValues = currentValues.includes(filter) ? currentValues.filter((v) => v !== filter) : [...currentValues, filter]; - const newParams = {}; - // Preserve other query parameters - Array.from(searchParams.entries()).forEach(([key, value]) => { - if (!newParams[key]) { - newParams[key] = []; + const newParams: Record = {}; + Array.from(searchParams.entries()).forEach(([paramKey, value]) => { + if (!newParams[paramKey]) { + newParams[paramKey] = []; } - newParams[key].push(value); + newParams[paramKey].push(value); }); - // Update the specific filter newParams[key] = newValues; startTransition(() => { @@ -102,12 +119,12 @@ function FilterLogic({ filters }) { [router, searchParams, createQueryString], ); - const activeFilters = new Map(); + const activeFilters = new Map(); Array.from(searchParams.entries()).forEach(([key, value]) => { if (!activeFilters.has(key)) { activeFilters.set(key, []); } - activeFilters.get(key).push(value); + activeFilters.get(key)!.push(value); }); return ( @@ -120,7 +137,11 @@ function FilterLogic({ filters }) { ); } -export default memo(function DevelopersResourcesFilters({ filters }) { +export default memo(function DevelopersResourcesFilters({ + filters, +}: { + filters: FilterMap; +}) { return ( {items.map((item, id) => ( @@ -27,14 +39,14 @@ function Grid({ items }) { ); } -function FilteredGrid({ items }) { +function FilteredGrid({ items }: { items: ResourceItem[] }) { const searchParams = useSearchParams(); const filteredItems = useMemo(() => { return items.filter((item) => { let matchesFilters = true; - const filterKeys = new Set(); + const filterKeys = new Set(); for (const [key] of searchParams.entries()) { filterKeys.add(key); } @@ -66,7 +78,11 @@ function FilteredGrid({ items }) { return ; } -export default memo(function DevelopersResourcesGrid({ items }) { +export default memo(function DevelopersResourcesGrid({ + items, +}: { + items: ResourceItem[]; +}) { return ( }> diff --git a/apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.jsx b/apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.tsx similarity index 69% rename from apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.jsx rename to apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.tsx index 1317e01cea..aa593a792d 100644 --- a/apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersContentSection/DevelopersContent.tsx @@ -1,12 +1,13 @@ import classNames from "classnames"; +import type { ReactNode } from "react"; import styles from "./DevelopersContent.module.scss"; -function Title({ children }) { +function Title({ children }: { children: ReactNode }) { return

{children}

; } -function Description({ children }) { +function Description({ children }: { children: ReactNode }) { return (

{children} @@ -14,7 +15,13 @@ function Description({ children }) { ); } -function Container({ className, children }) { +function Container({ + className, + children, +}: { + className?: string; + children: ReactNode; +}) { return (

{t("developers.courses.description")}

- {/* */}
@@ -81,7 +76,6 @@ export default function DevelopersCoursesSection(/* { courses } */) { image={solanaDevCourseSmallImg} /> {courses} - {/* */}
@@ -94,29 +88,8 @@ export default function DevelopersCoursesSection(/* { courses } */) { className={styles["feature-card"]} /> {courses} - {/* */}
); } - -// const CourseCards = memo(function CourseCards({ courses }) { -// // const t = useTranslations(); - -// return ( -// <> -// {courses.map((item, id) => ( -// -// ))} -// -// ); -// }); diff --git a/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.jsx b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.tsx similarity index 51% rename from apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.jsx rename to apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.tsx index 35801fb79f..9e37cd5672 100644 --- a/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersChangelog.tsx @@ -4,7 +4,21 @@ import Button from "../../../shared/Button"; import { useTranslations } from "next-intl"; import styles from "./DevelopersChangelog.module.scss"; -export default function DevelopersChangelog({ latestVideo }) { +export type LatestChangelogVideo = { + snippet: { + description?: string; + resourceId?: { + videoId?: string; + }; + playlistId?: string; + }; +}; + +export default function DevelopersChangelog({ + latestVideo, +}: { + latestVideo?: LatestChangelogVideo | null; +}) { const t = useTranslations(); if (!latestVideo) { return null; @@ -23,14 +37,17 @@ export default function DevelopersChangelog({ latestVideo }) { "...", )}

- + {latestVideo.snippet?.resourceId?.videoId && + latestVideo.snippet?.playlistId ? ( + + ) : null}
); } diff --git a/apps/web/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.jsx b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.tsx similarity index 86% rename from apps/web/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.jsx rename to apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.tsx index 6713ae7d1f..8ff6d9456b 100644 --- a/apps/web/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentItem.tsx @@ -4,13 +4,21 @@ import Button from "../../../shared/Button"; import { useTranslations } from "next-intl"; import styles from "./DevelopersDocumentItem.module.scss"; +type DevelopersDocumentItemProps = { + title: string; + description: string; + url: string; + newTab?: boolean; + ctaLabel?: string; +}; + export default function DevelopersDocumentItem({ title, description, url, newTab = true, ctaLabel, -}) { +}: DevelopersDocumentItemProps) { const t = useTranslations(); return ( diff --git a/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.jsx b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.tsx similarity index 89% rename from apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.jsx rename to apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.tsx index 8675c631ed..e44c134152 100644 --- a/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersDocumentsSection/DevelopersDocumentsSection.tsx @@ -2,7 +2,9 @@ import { useTranslations } from "next-intl"; import Image from "next/image"; import DevelopersDocumentItem from "./DevelopersDocumentItem"; -import DevelopersChangelog from "./DevelopersChangelog"; +import DevelopersChangelog, { + type LatestChangelogVideo, +} from "./DevelopersChangelog"; import styles from "./DevelopersDocumentsSection.module.scss"; @@ -11,7 +13,13 @@ import { InlineLink } from "../../../../utils/Link"; import DevelopersSectionTitle from "../DevelopersSectionTitle"; import { YT_PLAYLIST_CHANGELOG } from "@/constants/developerContentConfig"; -export default function DevelopersDocumentsSection({ latestVideo }) { +type DevelopersDocumentsSectionProps = { + latestVideo?: LatestChangelogVideo | null; +}; + +export default function DevelopersDocumentsSection({ + latestVideo, +}: DevelopersDocumentsSectionProps) { const t = useTranslations(); return ( diff --git a/apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.jsx b/apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.tsx similarity index 74% rename from apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.jsx rename to apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.tsx index 1b24e5c866..fd0ccf2ee8 100644 --- a/apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersHeroSection/DevelopersHeroSection.tsx @@ -1,14 +1,34 @@ import classNames from "classnames"; -import Image from "next/image"; +import Image, { type StaticImageData } from "next/image"; +import type { ReactNode } from "react"; import Button from "@/components/shared/Button"; import styles from "./DevelopersHeroSection.module.scss"; +type HeroSectionButton = { + href?: string; + label: string; + icon?: ReactNode; +}; + +type HeroSectionProps = { + img: { + src: StaticImageData | string; + alt?: string; + }; + title: string; + description: string; + buttons?: { + cta?: HeroSectionButton; + secondary?: HeroSectionButton; + }; +}; + const HeroSection = ({ img: { src, alt = "" }, title, description, buttons, -}) => { +}: HeroSectionProps) => { return (
{buttons.secondary.label} diff --git a/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.jsx b/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.tsx similarity index 77% rename from apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.jsx rename to apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.tsx index b61cc6830d..914a9af167 100644 --- a/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourceItem.tsx @@ -5,7 +5,17 @@ import { InlineLink, Link } from "@/utils/Link"; import { useTranslations } from "next-intl"; import styles from "./DevelopersResourceItem.module.scss"; -import { memo } from "react"; +import { type ReactNode } from "react"; + +type DevelopersResourceItemProps = { + category?: string; + children?: ReactNode; + title?: ReactNode; + description?: ReactNode; + url?: string; + isExternal?: boolean; + ctaLabel?: string; +}; export default function DevelopersResourceItem({ category = "Resource", @@ -15,19 +25,13 @@ export default function DevelopersResourceItem({ url, isExternal, ctaLabel, -}) { +}: DevelopersResourceItemProps) { const t = useTranslations(); - // use the correct type of link - const ResourceLink = memo( - ({ children, ...props }) => { - if (isExternal) return {children}; - else return {children}; - }, - [isExternal], - ); + const linkTo = url ?? "#"; + const ResourceLink = isExternal ? InlineLink : Link; return ( - +
{category}
diff --git a/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.jsx b/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.tsx similarity index 79% rename from apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.jsx rename to apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.tsx index 272ac9316d..95611eea83 100644 --- a/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersResourcesSection/DevelopersResourcesSection.tsx @@ -1,4 +1,4 @@ -import { memo } from "react"; +import { memo, ReactNode } from "react"; import { useTranslations } from "next-intl"; import CarouselCards from "@/components/shared/CarouselCards"; import DevelopersResourceItem from "./DevelopersResourceItem"; @@ -7,11 +7,26 @@ import DevelopersSectionTitle from "../DevelopersSectionTitle"; import styles from "./DevelopersResourcesSection.module.scss"; import Button from "@/components/shared/Button"; +export type ResourceItem = { + category?: string; + difficulty?: string; + title?: ReactNode; + description?: ReactNode; + href?: string; + isExternal?: boolean; +}; + +export type DevelopersResourcesSectionProps = { + items: ResourceItem[]; + baseHref?: string; + translationKey?: string; +}; + export default function DevelopersResourcesSection({ items, baseHref = "/developers/guides", translationKey = "guides", -}) { +}: DevelopersResourcesSectionProps) { const t = useTranslations(); return ( @@ -44,9 +59,11 @@ export default function DevelopersResourcesSection({ ); } -const ResourceCards = memo(function ResourceCards({ items }) { - // const t = useTranslations(); - +const ResourceCards = memo(function ResourceCards({ + items, +}: { + items: ResourceItem[]; +}) { return ( <> {items.map((item, id) => ( diff --git a/apps/docs/src/components/developers/sections/DevelopersSectionTitle.jsx b/apps/docs/src/components/developers/sections/DevelopersSectionTitle.tsx similarity index 69% rename from apps/docs/src/components/developers/sections/DevelopersSectionTitle.jsx rename to apps/docs/src/components/developers/sections/DevelopersSectionTitle.tsx index af02901a28..4a764ec8c8 100644 --- a/apps/docs/src/components/developers/sections/DevelopersSectionTitle.jsx +++ b/apps/docs/src/components/developers/sections/DevelopersSectionTitle.tsx @@ -1,7 +1,11 @@ import { useTranslations } from "next-intl"; import styles from "./DevelopersSectionTitle.module.scss"; -export default function DevelopersSectionTitle({ titleId }) { +export default function DevelopersSectionTitle({ + titleId, +}: { + titleId: string; +}) { const t = useTranslations(); return

{t(titleId)}

; } diff --git a/apps/docs/src/components/opengraph/DeveloperDocsImage.tsx b/apps/docs/src/components/opengraph/DeveloperDocsImage.tsx index fe67af5095..13ecd9b55e 100644 --- a/apps/docs/src/components/opengraph/DeveloperDocsImage.tsx +++ b/apps/docs/src/components/opengraph/DeveloperDocsImage.tsx @@ -6,10 +6,11 @@ import { import { ImageResponse } from "next/og"; import { config } from "@@/src/config"; import { generateRandomInRange } from "@@/src/utils/general"; +import { ReactNode } from "react"; type DeveloperDocsImageProps = { - heading: string; - title: string; + heading: ReactNode; + title?: ReactNode; }; // Image generation diff --git a/apps/web/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.jsx b/apps/docs/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.tsx similarity index 83% rename from apps/web/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.jsx rename to apps/docs/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.tsx index 1e42cd4ee2..f0f71eae2c 100644 --- a/apps/web/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.jsx +++ b/apps/docs/src/components/shared/EmailSubscribeForm/IterableEmailSubscribeForm.tsx @@ -1,7 +1,8 @@ import classNames from "classnames"; import { useTranslations } from "next-intl"; -import { memo, useMemo } from "react"; +import { memo, useMemo, type Ref } from "react"; import * as Yup from "yup"; +import type { AnySchema } from "yup"; import Button from "../Button"; import useIterableSignUp, { ActionForm } from "../Iterable/useIterableSignUp"; @@ -11,12 +12,18 @@ const Status = { Sending: "sending", Error: "error", Success: "success", -}; +} as const; + +type StatusValue = (typeof Status)[keyof typeof Status] | null; + +type StatusMessageProps = { status: StatusValue }; -const StatusMessage = memo(function StatusMessage({ status }) { +const StatusMessage = memo(function StatusMessage({ + status, +}: StatusMessageProps) { const t = useTranslations(); - if (!Object.values(Status).includes(status)) { + if (!status || !Object.values(Status).includes(status)) { return null; } @@ -42,6 +49,15 @@ const defaultValues = { email: "", }; +type IterableEmailSubscribeFormProps = { + inputRef?: Ref; + formId: string; + schema?: AnySchema; + initialValues?: Record; + placeholderTextID?: string; + ctaTextID?: string; +}; + export default function IterableEmailSubscribeForm({ inputRef, formId, @@ -49,7 +65,7 @@ export default function IterableEmailSubscribeForm({ initialValues = defaultValues, placeholderTextID, ctaTextID, -}) { +}: IterableEmailSubscribeFormProps) { const t = useTranslations(); const { @@ -72,7 +88,7 @@ export default function IterableEmailSubscribeForm({ return isDirty && isSubmitting && !schema.isValidSync(values); }, [schema, isDirty, isSubmitting, values]); - const status = useMemo(() => { + const status = useMemo(() => { if (error) { return Status.Error; } @@ -124,7 +140,7 @@ export default function IterableEmailSubscribeForm({ onClick={onSubmit} > {ctaTextID - ? t({ ctaTextID }) + ? t({ ctaTextID } as never) : t("shared.mail-signup.form.signup")}
diff --git a/apps/docs/src/components/shared/EmailSubscribeForm/index.js b/apps/docs/src/components/shared/EmailSubscribeForm/index.ts similarity index 100% rename from apps/docs/src/components/shared/EmailSubscribeForm/index.js rename to apps/docs/src/components/shared/EmailSubscribeForm/index.ts diff --git a/apps/web/src/components/shared/Iterable/useIterableSignUp.jsx b/apps/docs/src/components/shared/Iterable/useIterableSignUp.tsx similarity index 57% rename from apps/web/src/components/shared/Iterable/useIterableSignUp.jsx rename to apps/docs/src/components/shared/Iterable/useIterableSignUp.tsx index acf72c20d7..55ffa85c22 100644 --- a/apps/web/src/components/shared/Iterable/useIterableSignUp.jsx +++ b/apps/docs/src/components/shared/Iterable/useIterableSignUp.tsx @@ -1,9 +1,18 @@ -import { useCallback, useState } from "react"; +import { + useCallback, + useState, + type FormHTMLAttributes, + type ReactNode, +} from "react"; +import type { AnySchema } from "yup"; const ITERABLE_BASE_URL = "https://links.iterable.com/lists/publicAddSubscriberForm?publicIdString="; -async function sendFormRequest(actionUrl, dataObject) { +async function sendFormRequest( + actionUrl: string, + dataObject: Record, +): Promise { const data = new FormData(); Object.keys(dataObject).map((key) => { @@ -22,13 +31,18 @@ async function sendFormRequest(actionUrl, dataObject) { } } +type ActionFormProps = FormHTMLAttributes & { + children: ReactNode; + action?: string; +}; + export function ActionForm({ children, action, target = "_blank", method = "post", ...props -}) { +}: ActionFormProps) { return (
{children} @@ -36,33 +50,46 @@ export function ActionForm({ ); } -export default function useIterableSignUp({ formId, schema, initialValues }) { - const [state, setState] = useState(initialValues); +type UseIterableSignUpArgs = { + formId: string; + schema: AnySchema; + initialValues: Record; +}; + +export default function useIterableSignUp({ + formId, + schema, + initialValues, +}: UseIterableSignUpArgs) { + const [state, setState] = useState>(initialValues); const [isDirty, setIsDirty] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); + const [error, setError] = useState(null); const [isSuccess, setIsSuccess] = useState(false); const actionUrl = `${ITERABLE_BASE_URL}${formId}`; - const onValueChange = useCallback((event) => { - const { name, value } = event.target; + const onValueChange = useCallback( + (event: React.ChangeEvent) => { + const { name, value } = event.target; - setState((prevState) => ({ - ...prevState, - [name]: value, - })); + setState((prevState) => ({ + ...prevState, + [name]: value, + })); - setIsDirty(!!value); - setIsSubmitting(false); - setError(null); - setIsSuccess(false); - }, []); + setIsDirty(!!value); + setIsSubmitting(false); + setError(null); + setIsSuccess(false); + }, + [], + ); const onSubmit = useCallback( - async (e) => { + async (e: React.FormEvent | React.MouseEvent) => { e.preventDefault(); setIsDirty(true); diff --git a/apps/docs/src/components/shared/MarkdownRenderer/components/MarkdownEmbed.tsx b/apps/docs/src/components/shared/MarkdownRenderer/components/MarkdownEmbed.tsx index e618760b31..5e2ab55093 100644 --- a/apps/docs/src/components/shared/MarkdownRenderer/components/MarkdownEmbed.tsx +++ b/apps/docs/src/components/shared/MarkdownRenderer/components/MarkdownEmbed.tsx @@ -28,7 +28,9 @@ export const MarkdownEmbed = memo( /^(https:\/\/)?whimsical.com\/embed\/(?:[a-zA-Z0-9-]+-)?([a-km-zA-HJ-NP-Z1-9]{16,22})/gi, ).test(url) ) { - return ; + return ( + + ); } // youtube embeds @@ -64,7 +66,6 @@ const YouTubeEmbed = ({ videoId }: { videoId: string }) => { return (
diff --git a/apps/docs/src/constants/developerContentConfig.js b/apps/docs/src/constants/developerContentConfig.js deleted file mode 100644 index 39e44a91d6..0000000000 --- a/apps/docs/src/constants/developerContentConfig.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - // simple strings for the content record groups - RECORD_GROUPS: { - cookbook: "cookbook", - guides: "guides", - docs: "docs", - rpc: "docs/rpc", - }, - - /** - * the highest markdown heading size to include when generating a TOC - * (e.g. `3` results in "h1, h2, h3" to be displayed) - */ - TOC_HEADING_SIZE: 3, - - /** - * Define some common content values - */ - YT_PLAYLIST_CHANGELOG: "PLilwLeBwGuK5-Qri7Pg9zd-Vvhz9kX2-R", -}; diff --git a/apps/docs/src/constants/developerContentConfig.ts b/apps/docs/src/constants/developerContentConfig.ts new file mode 100644 index 0000000000..f88aea5480 --- /dev/null +++ b/apps/docs/src/constants/developerContentConfig.ts @@ -0,0 +1,18 @@ +// simple strings for the content record groups +export const RECORD_GROUPS = { + cookbook: "cookbook", + guides: "guides", + docs: "docs", + rpc: "docs/rpc", +} as const; + +/** + * the highest markdown heading size to include when generating a TOC + * (e.g. `3` results in "h1, h2, h3" to be displayed) + */ +export const TOC_HEADING_SIZE = 3; + +/** + * Define some common content values + */ +export const YT_PLAYLIST_CHANGELOG = "PLilwLeBwGuK5-Qri7Pg9zd-Vvhz9kX2-R"; diff --git a/apps/web/src/hooks/useIsomorphicLayoutEffect.js b/apps/docs/src/hooks/useIsomorphicLayoutEffect.ts similarity index 64% rename from apps/web/src/hooks/useIsomorphicLayoutEffect.js rename to apps/docs/src/hooks/useIsomorphicLayoutEffect.ts index c8748e78dc..59171d4fa4 100644 --- a/apps/web/src/hooks/useIsomorphicLayoutEffect.js +++ b/apps/docs/src/hooks/useIsomorphicLayoutEffect.ts @@ -2,8 +2,6 @@ import { useEffect, useLayoutEffect } from "react"; /** * Only uses useLayoutEffect() on the frontend side and not during SSR. - * - * @type {{(effect: React.EffectCallback, deps?: React.DependencyList): void, (effect: React.EffectCallback, deps?: React.DependencyList): void}} */ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect; diff --git a/apps/docs/src/lib/builder/builderConstants.js b/apps/docs/src/lib/builder/builderConstants.ts similarity index 100% rename from apps/docs/src/lib/builder/builderConstants.js rename to apps/docs/src/lib/builder/builderConstants.ts diff --git a/apps/docs/src/utils/Link.js b/apps/docs/src/utils/Link.ts similarity index 100% rename from apps/docs/src/utils/Link.js rename to apps/docs/src/utils/Link.ts diff --git a/apps/web/src/utils/dateUtils.js b/apps/docs/src/utils/dateUtils.ts similarity index 55% rename from apps/web/src/utils/dateUtils.js rename to apps/docs/src/utils/dateUtils.ts index 16f8b90011..a5293c676e 100644 --- a/apps/web/src/utils/dateUtils.js +++ b/apps/docs/src/utils/dateUtils.ts @@ -1,9 +1,9 @@ import { formatInTimeZone } from "date-fns-tz"; import { isString } from "./stringUtils"; import { add, format } from "date-fns"; -import { parse as parseDurationString } from "tinyduration"; +import { parse as parseDurationString, type Duration } from "tinyduration"; -export const defaultDateStringOptions = { +export const defaultDateStringOptions: Intl.DateTimeFormatOptions = { weekday: "long", year: "numeric", month: "long", @@ -12,28 +12,20 @@ export const defaultDateStringOptions = { /** * Uses intl to get the locale & formats the given string accordingly. - * - * @param locale - * @param dateString The date to convert. - * @param dateStringOptions Options for toLocaleDateString. - * @returns {string} */ export const toLocaleString = ( - locale, - dateString, - dateStringOptions = defaultDateStringOptions, -) => new Date(dateString).toLocaleDateString(locale, dateStringOptions); + locale: string | string[] | undefined, + dateString: string | number | Date, + dateStringOptions: Intl.DateTimeFormatOptions = defaultDateStringOptions, +): string => new Date(dateString).toLocaleDateString(locale, dateStringOptions); /** * Formats a given date to MM/DD/YYYY HH:MM. - * - * @param {Date|string} date The given date. - * @returns {string} */ -export const formatDateTime = (date) => { +export const formatDateTime = (date: Date | string | number): string => { const dateTime = new Date(date); - let hours = dateTime.getUTCHours(); - let minutes = dateTime.getUTCMinutes(); + let hours: number | string = dateTime.getUTCHours(); + let minutes: number | string = dateTime.getUTCMinutes(); hours = hours < 10 ? `0${hours}` : hours; minutes = minutes < 10 ? `0${minutes}` : minutes; return `${ @@ -43,11 +35,8 @@ export const formatDateTime = (date) => { /** * Tries to switch the month and day part of a given Date string. - * - * @param dateString - * @returns {string} */ -export const switchMonthAndDay = (dateString) => { +export const switchMonthAndDay = (dateString: string): string => { try { const dateArray = dateString.split("-"); return [dateArray[0], dateArray[2], dateArray[1]].join("-"); @@ -59,33 +48,23 @@ export const switchMonthAndDay = (dateString) => { /** * Tries to fix a given Date string. - * - * @param {string} dateString The Date string to try to fix. - * @returns {Date|string} */ -export const fixDate = (dateString) => { +export const fixDate = (dateString: string): Date | string => { try { const parsedDate = new Date(dateString); if (Object.prototype.toString.call(parsedDate) === "[object Date]") { - // It is a Date object, but is it valid? if (isNaN(parsedDate.getTime())) { - // Date isn't valid, try to switch month & day & retry. const possiblyFixedDateString = switchMonthAndDay(dateString); const possiblyFixedDate = fixDate(possiblyFixedDateString); if (isString(possiblyFixedDate)) { - // Didn't work, return as string. return dateString; } - // Worked, return new Date object. return possiblyFixedDate; } - // Return valid Date object. return parsedDate; } - // Not a Date, return as string. return dateString; } catch (err) { - // Errored out, return as string. console.error(err); return dateString; } @@ -93,24 +72,20 @@ export const fixDate = (dateString) => { /** * Tries to format a DateTime in a given time zone. - * - * @param date - * @param dateFormat - * @param timezone - * @returns {string} */ -export const formatDate = (date, dateFormat, timezone) => +export const formatDate = ( + date: Date | number, + dateFormat: string, + timezone?: string, +): string => !!timezone && timezone !== "undefined" ? formatInTimeZone(date, timezone, dateFormat) : format(date, dateFormat); /** * Converts ISO-8601 Duration string to Duration obj - * - * @param {string} duration ISO-8601 Duration String - * @returns {(Duration|null)} */ -export function parseDuration(duration) { +export function parseDuration(duration: string): Duration | null { try { return parseDurationString(duration); } catch (error) { @@ -121,12 +96,8 @@ export function parseDuration(duration) { /** * Converts and adds ISO-8601 Duration to a given DateTime - * - * @param {DateTime} date - * @param {string} duration ISO-8601 Duration - * @returns {(DateTime|null)} */ -export function addDuration(date, duration) { +export function addDuration(date: Date | number, duration: string): Date { const durationObj = parseDuration(duration); - return durationObj ? add(date, durationObj) : date; + return durationObj ? add(date, durationObj) : new Date(date); } diff --git a/apps/docs/src/utils/followerFunctions.js b/apps/docs/src/utils/followerFunctions.ts similarity index 69% rename from apps/docs/src/utils/followerFunctions.js rename to apps/docs/src/utils/followerFunctions.ts index 08b12ccaef..860a40b22e 100644 --- a/apps/docs/src/utils/followerFunctions.js +++ b/apps/docs/src/utils/followerFunctions.ts @@ -1,9 +1,22 @@ import * as cheerio from "cheerio"; import cachedFetch from "node-fetch-cache"; +export type YTVideoItem = { + id?: string; + snippet: { + thumbnails?: Record; + title: string; + description?: string; + playlistId?: string; + resourceId?: { videoId?: string }; + publishedAt?: string; + }; + contentDetails: { videoId: string }; +}; + const ytBaseURL = "https://www.googleapis.com/youtube/v3/"; -const scrapeUrlForTag = async (url, tagName) => { +const scrapeUrlForTag = async (url: string, tagName: string) => { const siteData = await cachedFetch(url) .then((res) => res.text()) .catch((err) => { @@ -23,8 +36,17 @@ const scrapeMeetupMemberCount = async () => { `https://www.meetup.com/topics/solana/`, `div.font-medium`, ); - if (meetupMemberCountTag.length) { - const meetupCountString = meetupMemberCountTag[0].children[0].data; + if ( + meetupMemberCountTag.length && + meetupMemberCountTag[0] && + typeof meetupMemberCountTag[0] === "object" + ) { + const meetupCountString = + "children" in meetupMemberCountTag[0] && + meetupMemberCountTag[0].children[0] && + "data" in meetupMemberCountTag[0].children[0] + ? meetupMemberCountTag[0].children[0].data + : ""; return parseInt(meetupCountString.replace(",", ""), 10); } return 0; @@ -51,7 +73,12 @@ const getYoutubeSubscriberCount = async () => { throw new Error("Channel statistics not found"); } catch (error) { - console.error("Error getting subscriber count:", error.message); + console.error( + "Error getting subscriber count:", + error && typeof error === "object" && "message" in error + ? error.message + : error, + ); throw error; } }; @@ -62,13 +89,17 @@ const getStableCoins = async () => { try { const jsonData = await cachedFetch(url, options); - const stableCoins = await jsonData.json(); - const chainData = stableCoins.data[0].chains; + const stableCoins = (await jsonData.json()) as { + data?: { chains: { chain: string; amount: number }[] }[]; + }; + const chainData = stableCoins.data?.[0]?.chains; let solAmount = 0; - for (let chain of chainData) { - if (chain.chain === "SOL") { - solAmount = chain.amount; - break; + if (chainData) { + for (const chain of chainData) { + if (chain.chain === "SOL") { + solAmount = chain.amount; + break; + } } } return solAmount; @@ -88,15 +119,15 @@ const getGHStargazers = async () => { }, }, ); - const jsonData = await res.json(); + const jsonData = (await res.json()) as { stargazers_count?: number }; return jsonData?.stargazers_count || 0; }; const getYTVideos = async ( - maxVideos = 50, - playlistId, - channelId = process.env.YOUTUBE_CHANNEL_ID, -) => { + maxVideos: number = 50, + playlistId: string, + channelId: string | undefined = process.env.YOUTUBE_CHANNEL_ID, +): Promise => { const apiKey = process.env.YOUTUBE_API_KEY; if (!apiKey) { @@ -119,9 +150,9 @@ const getYTVideos = async ( ]; } - let pageSize = Math.min(50, maxVideos); - let videos = []; - let videoResp; + const pageSize = Math.min(50, maxVideos); + const videos: YTVideoItem[] = []; + let videoResp: Response | undefined; // Playlist videos if (playlistId) { @@ -149,13 +180,16 @@ const getYTVideos = async ( } } + if (!videoResp) return videos; + const videosData = await videoResp.json(); if (!videosData.error) { // No thumbnails usually means the video has been deleted from a list videos.push( ...(videosData?.items?.filter( - (item) => Object.keys(item.snippet.thumbnails).length, + (item: YTVideoItem) => + Object.keys(item.snippet.thumbnails as object).length, ) || []), ); } else { diff --git a/apps/web/src/utils/stringUtils.js b/apps/docs/src/utils/stringUtils.ts similarity index 52% rename from apps/web/src/utils/stringUtils.js rename to apps/docs/src/utils/stringUtils.ts index 1fe47138ea..bb371432f5 100644 --- a/apps/web/src/utils/stringUtils.js +++ b/apps/docs/src/utils/stringUtils.ts @@ -1,12 +1,11 @@ /** * Returns a string truncated to a given length with an optional postfix. - * - * @param {string} text The text string to truncate. - * @param {number} truncateLength To which length to truncate. - * @param {string} append Postfix (e.g. Ellipsis). - * @returns {string} */ -export const truncateTextByWord = (text, truncateLength, append = "") => { +export const truncateTextByWord = ( + text: string, + truncateLength: number, + append: string = "", +): string => { const textLength = text.length; const appendLength = append.length; @@ -22,7 +21,6 @@ export const truncateTextByWord = (text, truncateLength, append = "") => { ? truncateLength - appendLength : textLength; - // Truncate string to new length & find last whitespace beforehand. const tempString = text.substring(0, newLength).replace(/\s+\S*$/, ""); if (appendLength > 0) { @@ -33,20 +31,14 @@ export const truncateTextByWord = (text, truncateLength, append = "") => { /** * Capitalizes first Char. - * - * @param {string} str Word to capitalize. - * @returns {string} */ -export const capitalizeFirstChar = (str) => +export const capitalizeFirstChar = (str: string): string => str ? str.charAt(0).toUpperCase() + str.slice(1) : ""; /** * Capitalizes every word in a given string. - * - * @param {string} text String to capitalize. - * @returns {string} */ -export const capitalize = (text) => +export const capitalize = (text: string): string => text .trim() .toLowerCase() @@ -54,19 +46,14 @@ export const capitalize = (text) => /** * Checks if a given text is a string. - * - * @param {string?} text The text to check. - * @returns {boolean} */ -export const isString = (text) => +export const isString = (text: unknown): boolean => typeof text === "string" || text instanceof String; /** * Takes a given title and converts it to a slug. - * - * @param title - * @param replacement - * @returns {string} */ -export const createSlugFromTitle = (title, replacement = "") => - title.replace(/\s+/g, replacement).toLowerCase(); +export const createSlugFromTitle = ( + title: string, + replacement: string = "", +): string => title.replace(/\s+/g, replacement).toLowerCase(); diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index d7e0c442c6..ddb338d070 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -3,7 +3,7 @@ "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, "forceConsistentCasingInFileNames": true, "noEmit": false, "incremental": true, @@ -23,7 +23,7 @@ "name": "next" } ], - "strictNullChecks": false, + "strictNullChecks": true, "target": "ES2017", "types": ["next", "node"] }, diff --git a/apps/media/__tests__/google-news-sitemap.test.ts b/apps/media/__tests__/google-news-sitemap.test.ts index b1862c61fa..e0921d9d46 100644 --- a/apps/media/__tests__/google-news-sitemap.test.ts +++ b/apps/media/__tests__/google-news-sitemap.test.ts @@ -16,7 +16,7 @@ vi.mock("@/lib/reader", () => ({ import { reader } from "@/lib/reader"; import { getGoogleNewsSitemapResponse } from "@/lib/google-news-sitemap"; -const mockReader = reader as any; +const mockReader = reader; describe("getGoogleNewsSitemapResponse", () => { beforeEach(() => { diff --git a/apps/media/app/[locale]/layout.tsx b/apps/media/app/[locale]/layout.tsx index 0d63f44d76..80ecb5ff6e 100644 --- a/apps/media/app/[locale]/layout.tsx +++ b/apps/media/app/[locale]/layout.tsx @@ -123,7 +123,10 @@ export default async function LocaleLayout({ children, params }: Props) { - +
{children}
diff --git a/apps/media/app/[locale]/news/category/[category]/page.tsx b/apps/media/app/[locale]/news/category/[category]/page.tsx index 4ea17ffcdd..eda4e84516 100644 --- a/apps/media/app/[locale]/news/category/[category]/page.tsx +++ b/apps/media/app/[locale]/news/category/[category]/page.tsx @@ -29,7 +29,7 @@ export default async function CategoryPostsPage({ try { const { category } = await fetchCategoryByPath(categoryParam); categoryName = category?.name || null; - } catch (error) { + } catch { return notFound(); } @@ -39,7 +39,7 @@ export default async function CategoryPostsPage({ try { latestPosts = await fetchLatestPosts({ limit: 13, category: categoryName }); - } catch (error) { + } catch { return notFound(); } diff --git a/apps/media/app/[locale]/news/page.tsx b/apps/media/app/[locale]/news/page.tsx index b5b920d9f7..1a5e663a0d 100644 --- a/apps/media/app/[locale]/news/page.tsx +++ b/apps/media/app/[locale]/news/page.tsx @@ -10,7 +10,7 @@ export async function generateMetadata(): Promise { } export default async function PostsPage({ - params, + params: _, }: { params: Promise<{ locale: string }>; }) { diff --git a/apps/media/app/[locale]/podcasts/[podcastSlug]/client-page.tsx b/apps/media/app/[locale]/podcasts/[podcastSlug]/client-page.tsx index 799b8b650d..b0a3cfa5bf 100644 --- a/apps/media/app/[locale]/podcasts/[podcastSlug]/client-page.tsx +++ b/apps/media/app/[locale]/podcasts/[podcastSlug]/client-page.tsx @@ -112,9 +112,9 @@ export default function PodcastShowClientPage({

{podcast.title}

- {podcast.description && ( + {podcast.description ? ( - )} + ) : null}
{/* Hosts */} diff --git a/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/client-page.tsx b/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/client-page.tsx index 7d2d770d41..751ee2c85c 100644 --- a/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/client-page.tsx +++ b/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/client-page.tsx @@ -3,7 +3,7 @@ import React from "react"; import { Link } from "@workspace/i18n/routing"; import Image from "next/image"; -import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from "lucide-react"; +import { ArrowLeft, ChevronLeft, ChevronRight } from "lucide-react"; import { motion } from "motion/react"; import { Section } from "@/components/layout/section"; import { Button } from "@/components/ui/button"; diff --git a/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/page.tsx b/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/page.tsx index fed2c552be..11503b69d1 100644 --- a/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/page.tsx +++ b/apps/media/app/[locale]/podcasts/[podcastSlug]/episodes/[episodeId]/page.tsx @@ -62,8 +62,8 @@ export default async function EpisodePage({ params }: EpisodePageProps) { podcast={podcast} episode={episode} relatedEpisodes={relatedEpisodes} - previousEpisode={previousEpisode} - nextEpisode={nextEpisode} + previousEpisode={previousEpisode ?? null} + nextEpisode={nextEpisode ?? null} /> ); } diff --git a/apps/media/app/[locale]/podcasts/page.tsx b/apps/media/app/[locale]/podcasts/page.tsx index e74f09c1d2..a98f3179dd 100644 --- a/apps/media/app/[locale]/podcasts/page.tsx +++ b/apps/media/app/[locale]/podcasts/page.tsx @@ -14,7 +14,7 @@ export const revalidate = 1800; // 30 minutes export const metadata: Metadata = podcastsListingMetadata(); export default async function PodcastsPage({ - params, + params: _, }: { params: Promise<{ locale: string }>; }) { diff --git a/apps/media/app/[locale]/reports/[slug]/page.tsx b/apps/media/app/[locale]/reports/[slug]/page.tsx index 74c55db8da..23864fff8e 100644 --- a/apps/media/app/[locale]/reports/[slug]/page.tsx +++ b/apps/media/app/[locale]/reports/[slug]/page.tsx @@ -13,6 +13,7 @@ import { reader } from "@/lib/reader"; import { reportMetadata } from "@/lib/metadata"; import { formatPublishedAt } from "@/lib/keystatic/publishing"; import { isPublishedReport } from "@/lib/keystatic/report-status"; +import { SwitchbackItem } from "@/lib/switchback-types"; export const revalidate = 300; export const dynamicParams = true; @@ -23,7 +24,8 @@ export default async function ReportPage({ params: Promise<{ locale: string; slug: string }>; }) { const { slug } = await params; - const report = await reader.collections.switchbacks.read(slug); + const report: SwitchbackItem = + await reader.collections.switchbacks.read(slug); if (!isPublishedReport(report)) { notFound(); @@ -32,7 +34,9 @@ export default async function ReportPage({ const formattedDate = formatPublishedAt(report.publishedAt, "long"); const headline = String(report.headline || report.title); const buttons = - report.buttons?.filter((button) => button?.label && button?.url) || []; + report.buttons?.filter((button): button is { label: string; url: string } => + Boolean(button?.label && button?.url), + ) || []; const categories = report.categories ? ( await Promise.all( diff --git a/apps/media/app/api/link-metadata/route.ts b/apps/media/app/api/link-metadata/route.ts index f43e3f9bee..599cc61d1e 100644 --- a/apps/media/app/api/link-metadata/route.ts +++ b/apps/media/app/api/link-metadata/route.ts @@ -27,7 +27,7 @@ function extractYouTubeVideoId(url: string): string | null { for (const pattern of patterns) { const match = url.match(pattern); - if (match) return match[1]; + if (match) return match[1] ?? null; } return null; } @@ -37,7 +37,8 @@ function extractYouTubeVideoId(url: string): string | null { */ function extractGitHubRepo(url: string): string | null { const match = url.match(/github\.com\/([^/]+\/[^/]+)/); - return match ? match[1] : null; + if (match) return match[1] ?? null; + return null; } /** @@ -69,7 +70,7 @@ async function fetchMetadataForUrl(url: string): Promise { if (response.ok) { const html = await response.text(); const titleMatch = html.match(/]*>([^<]+)<\/title>/i); - if (titleMatch) { + if (titleMatch && titleMatch[1]) { metadata.title = titleMatch[1] .replace(" - YouTube", "") .replace(/&/g, "&") @@ -78,7 +79,7 @@ async function fetchMetadataForUrl(url: string): Promise { const descMatch = html.match( /]*name=["']description["'][^>]*content=["']([^"']+)["']/i, ); - if (descMatch) { + if (descMatch && descMatch[1]) { metadata.description = descMatch[1] .replace(/&/g, "&") .replace(/'/g, "'"); @@ -133,7 +134,7 @@ async function fetchMetadataForUrl(url: string): Promise { "i", ), ); - if (match) return match[1]; + if (match) return match[1] ?? null; // Try content="..." property="..." match = html.match( @@ -142,7 +143,8 @@ async function fetchMetadataForUrl(url: string): Promise { "i", ), ); - return match ? match[1] : null; + if (match) return match[1] ?? null; + return null; }; // Extract Open Graph tags @@ -164,7 +166,7 @@ async function fetchMetadataForUrl(url: string): Promise { // Fallback to standard meta tags if OG tags not found if (!metadata.title) { const titleMatch = html.match(/]*>([^<]+)<\/title>/i); - if (titleMatch) { + if (titleMatch && titleMatch[1]) { metadata.title = titleMatch[1] .replace(/&/g, "&") .replace(/'/g, "'"); diff --git a/apps/media/components/blocks/stats.tsx b/apps/media/components/blocks/stats.tsx index cf9f7cf5f6..9483109c3a 100644 --- a/apps/media/components/blocks/stats.tsx +++ b/apps/media/components/blocks/stats.tsx @@ -16,7 +16,7 @@ const getGradientClass = (index: number): string => { "bg-gradient-to-r from-[#1fcff1] via-[#234cb6] to-[rgba(35,76,182,0.1)] bg-clip-text text-transparent", "bg-gradient-to-r from-[#19fb9b] via-[#199890] to-[#005f59] bg-clip-text text-transparent", ]; - return gradients[index % 3]; + return gradients[index % 3] ?? gradients[0]!; }; export const Stats = ({ data }: { data: StatsBlockData }) => { diff --git a/apps/media/components/icon.tsx b/apps/media/components/icon.tsx index bfad3b9cc1..221e9fa4d2 100644 --- a/apps/media/components/icon.tsx +++ b/apps/media/components/icon.tsx @@ -1,5 +1,4 @@ "use client"; -//TODO: Fix Types in this file import * as BoxIcons from "react-icons/bi"; import { @@ -10,11 +9,11 @@ import { FaYoutube, } from "react-icons/fa6"; import { AiFillInstagram } from "react-icons/ai"; -import React from "react"; +import React, { SVGProps } from "react"; import { useLayout } from "./layout/layout-context"; export const IconOptions = { - Tina: (props: any) => ( + Tina: (props: SVGProps) => ( { const { theme } = useLayout(); - //@ts-ignore if (IconOptions[data.name] === null || IconOptions[data.name] === undefined) { return null; } const { name, color, size = "medium", style = "regular" } = data; - //@ts-ignore const IconSVG = IconOptions[name]; - //@ts-ignore const iconSizeClasses = typeof size === "string" ? iconSizeClass[size as keyof typeof iconSizeClass] @@ -138,7 +133,7 @@ export const Icon = ({ return (
@@ -150,7 +145,7 @@ export const Icon = ({ (iconColor === theme!.color || iconColor === "primary") ? "white" : iconColor! - ].regular; + ]?.regular; return ( { sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw" className="object-cover group-hover:scale-105 transition-transform duration-300" loading="lazy" - unoptimized={hasExternalImage} + unoptimized={hasExternalImage || undefined} />
)} @@ -93,7 +93,7 @@ export const LinkCard = ({ link }: LinkCardProps) => {

{link.title}

- {link.description && ( + {link.description ? (
{typeof link.description === "string" ? (

{link.description}

@@ -101,7 +101,7 @@ export const LinkCard = ({ link }: LinkCardProps) => {

{String(link.description)}

)}
- )} + ) : null}
{link.tags?.map( (tag: string) => diff --git a/apps/media/components/magicui/script-copy-btn.tsx b/apps/media/components/magicui/script-copy-btn.tsx index 6b6ddfc611..1da7d76f59 100644 --- a/apps/media/components/magicui/script-copy-btn.tsx +++ b/apps/media/components/magicui/script-copy-btn.tsx @@ -31,8 +31,9 @@ export function ScriptCopyBtn({ const [highlightedCode, setHighlightedCode] = useState(""); const { theme } = useTheme(); const command = - commands.find((line) => line.startsWith(packageManager))?.split("|")[1] || - ""; + commands + .find((line) => line.startsWith(packageManager ?? "")) + ?.split("|")[1] || ""; useEffect(() => { async function loadHighlightedCode() { diff --git a/apps/media/components/mdx-components.tsx b/apps/media/components/mdx-components.tsx index 7b43b5c72f..b12352e689 100644 --- a/apps/media/components/mdx-components.tsx +++ b/apps/media/components/mdx-components.tsx @@ -1,7 +1,7 @@ import { format } from "date-fns"; import React from "react"; import type { ReactNode, ElementType } from "react"; -import Image from "next/image"; +import Image, { ImageProps } from "next/image"; import { Video } from "./blocks/video"; import { Mermaid } from "./blocks/mermaid"; import { Tweet } from "react-tweet"; @@ -368,7 +368,7 @@ export const components = { gallery: (props: { background?: string; images?: GalleryImage[] }) => { // Cast to any to avoid type conflicts between Keystatic schema and Gallery component - return ; + return ; }, stats: (props: StatsBlockData) => { @@ -397,7 +397,7 @@ export const components = { ), }, } as DocumentRendererProps["renderers"] & { - block?: Record React.ReactNode>; + block?: Record>; }; // Custom component tags used in MDX content (from Keystatic component blocks). @@ -500,7 +500,7 @@ const StatsBlock = (props: StatsBlockData) => { const statsData = { title: props.title || "", description: props.description || "", - stats: props.stats?.map((stat: any) => ({ + stats: props.stats?.map((stat) => ({ stat: stat?.stat, type: stat?.type, })), @@ -590,7 +590,7 @@ const SupBlock = (props: { children: React.ReactNode }) => ( // - Capitalized names: for custom inline JSX components (after preprocessMDX capitalizes tags) // MDX v3 resolves capitalized JSX from props.components via destructuring // - Lowercase names: for markdown-generated HTML element overrides (img, blockquote from > syntax) -export const mdxComponents: Record> = { +export const mdxComponents = { // Capitalized custom components (resolved by MDX v3 for inline JSX) Tweet: TweetBlock, Video: VideoBlock, @@ -604,7 +604,7 @@ export const mdxComponents: Record> = { Sup: SupBlock, // Lowercase overrides for markdown-generated elements blockquote: BlockquoteBlock, - img: ({ src, alt }: any) => { + img: ({ src, alt }: { src?: ImageProps["src"]; alt?: ImageProps["alt"] }) => { if (!src) return null; return ( diff --git a/apps/media/components/motion-primitives/text-effect.tsx b/apps/media/components/motion-primitives/text-effect.tsx index 84740a84d7..5a2f75c92a 100644 --- a/apps/media/components/motion-primitives/text-effect.tsx +++ b/apps/media/components/motion-primitives/text-effect.tsx @@ -184,8 +184,8 @@ const createVariantsWithTransition = ( visible: { ...baseVariants.visible, transition: { - ...(hasTransition(baseVariants.visible) - ? baseVariants.visible.transition + ...(hasTransition(baseVariants.visible ?? {}) + ? (baseVariants.visible as TargetAndTransition).transition : {}), ...mainTransition, }, @@ -193,8 +193,8 @@ const createVariantsWithTransition = ( exit: { ...baseVariants.exit, transition: { - ...(hasTransition(baseVariants.exit) - ? baseVariants.exit.transition + ...(hasTransition(baseVariants.exit ?? {}) + ? (baseVariants.exit as TargetAndTransition).transition : {}), ...mainTransition, staggerDirection: -1, diff --git a/apps/media/components/podcast/audio-player.tsx b/apps/media/components/podcast/audio-player.tsx index ca69547447..8e0b9cd2c6 100644 --- a/apps/media/components/podcast/audio-player.tsx +++ b/apps/media/components/podcast/audio-player.tsx @@ -108,7 +108,7 @@ export const AudioPlayer = ({ const rates = [0.5, 0.75, 1, 1.25, 1.5, 2]; const currentIndex = rates.indexOf(globalPlayer.playbackRate); const nextIndex = (currentIndex + 1) % rates.length; - globalPlayer.setPlaybackRate(rates[nextIndex]); + globalPlayer.setPlaybackRate(rates[nextIndex] ?? 1); }, [isGlobalEpisode, globalPlayer]); const getProgressFromPosition = useCallback((clientX: number) => { diff --git a/apps/media/components/podcast/player-context.tsx b/apps/media/components/podcast/player-context.tsx index b2d366b433..77bbe9d09e 100644 --- a/apps/media/components/podcast/player-context.tsx +++ b/apps/media/components/podcast/player-context.tsx @@ -28,18 +28,18 @@ interface PlayerContextValue { isMuted: boolean; playbackRate: number; play: ( - episode: PodcastEpisode, - podcastTitle?: string, - podcastSlug?: string, + _episode: PodcastEpisode, + _podcastTitle?: string, + _podcastSlug?: string, ) => void; pause: () => void; togglePlayPause: () => void; - seek: (fraction: number) => void; - skipForward: (seconds?: number) => void; - skipBackward: (seconds?: number) => void; - setVolume: (volume: number) => void; + seek: (_fraction: number) => void; + skipForward: (_seconds?: number) => void; + skipBackward: (_seconds?: number) => void; + setVolume: (_volume: number) => void; toggleMute: () => void; - setPlaybackRate: (rate: number) => void; + setPlaybackRate: (_rate: number) => void; setProgress: (_progress: number) => void; setDuration: (_duration: number) => void; dismiss: () => void; diff --git a/apps/media/components/podcast/podcast-card.tsx b/apps/media/components/podcast/podcast-card.tsx index aa600f6cbe..b3d8111bea 100644 --- a/apps/media/components/podcast/podcast-card.tsx +++ b/apps/media/components/podcast/podcast-card.tsx @@ -81,7 +81,7 @@ export const PodcastCard = ({
{podcast.hosts.length === 1 - ? podcast.hosts[0].name + ? podcast.hosts[0]?.name : podcast.hosts.map((h) => h.name).join(", ")}
diff --git a/apps/media/components/post/post-card.tsx b/apps/media/components/post/post-card.tsx index 4b6239c815..5492a79ad7 100644 --- a/apps/media/components/post/post-card.tsx +++ b/apps/media/components/post/post-card.tsx @@ -2,7 +2,10 @@ import Link from "next/link"; import Image from "next/image"; import { Badge } from "@/components/ui/badge"; import { PostItem } from "@/lib/post-types"; -import { DocumentRenderer } from "@keystatic/core/renderer"; +import { + DocumentRenderer, + DocumentRendererProps, +} from "@keystatic/core/renderer"; import { components } from "@/components/mdx-components"; import { ArrowUpRight } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -17,8 +20,14 @@ function getUniqueValues(values: string[] | undefined): string[] { return Array.from(new Set(values.filter(Boolean))); } +interface DescriptionContentProps { + description?: + | DocumentRendererProps["document"] + | { node: { children: DocumentRendererProps["document"] } }; +} + // Helper: render description as plain text or DocumentRenderer document -function DescriptionContent({ description }: { description: any }) { +function DescriptionContent({ description }: DescriptionContentProps) { if (!description) return null; // Plain string from fields.text() @@ -33,7 +42,8 @@ function DescriptionContent({ description }: { description: any }) { // Object with node.children if (typeof description === "object" && "node" in description) { - const children = description.node?.children; + const children = + "children" in description.node ? description.node?.children : null; if (Array.isArray(children)) { return ; } @@ -79,7 +89,11 @@ export const PostCard = ({ post, variant = "vertical" }: PostCardProps) => { )}
- +
@@ -128,7 +142,11 @@ export const PostCard = ({ post, variant = "vertical" }: PostCardProps) => { {post.title}
- +
Read article diff --git a/apps/media/components/raw-renderer.tsx b/apps/media/components/raw-renderer.tsx index 1d50613b0c..73abce7f8e 100644 --- a/apps/media/components/raw-renderer.tsx +++ b/apps/media/components/raw-renderer.tsx @@ -10,8 +10,13 @@ import { } from "@headlessui/react"; import { useLayout } from "./layout/layout-context"; -//@ts-ignore -export const RawRenderer = ({ rawData, parentColor }) => { +export const RawRenderer = ({ + rawData, + parentColor, +}: { + rawData?: object; + parentColor?: string; +}) => { const { theme } = useLayout(); const buttonColorClasses = { blue: "text-blue-500", @@ -38,7 +43,6 @@ export const RawRenderer = ({ rawData, parentColor }) => {
); } diff --git a/apps/web/src/app/[locale]/community/page.tsx b/apps/web/src/app/[locale]/community/page.tsx index 0eee75112b..089d589fb7 100644 --- a/apps/web/src/app/[locale]/community/page.tsx +++ b/apps/web/src/app/[locale]/community/page.tsx @@ -24,9 +24,9 @@ export default async function Page(_props: Props) { ]); const socialData = { - youtube: youtube.status === "fulfilled" ? youtube.value : null, - github: github.status === "fulfilled" ? github.value : null, - meetup: meetup.status === "fulfilled" ? meetup.value : null, + youtube: youtube.status === "fulfilled" ? youtube.value : undefined, + github: github.status === "fulfilled" ? github.value : undefined, + meetup: meetup.status === "fulfilled" ? meetup.value : undefined, }; return ( diff --git a/apps/web/src/app/[locale]/community/report-2024-newsletter-sign-up/community-report-2024-newsletter-sign-up.tsx b/apps/web/src/app/[locale]/community/report-2024-newsletter-sign-up/community-report-2024-newsletter-sign-up.tsx index c06192ac5e..b5231a6a2f 100644 --- a/apps/web/src/app/[locale]/community/report-2024-newsletter-sign-up/community-report-2024-newsletter-sign-up.tsx +++ b/apps/web/src/app/[locale]/community/report-2024-newsletter-sign-up/community-report-2024-newsletter-sign-up.tsx @@ -21,7 +21,7 @@ export function CommunityReport2024NewsletterSignUpPage({ return (
- + ["cards"] + } + numCols={CARD_DECK_COLUMNS} + /> - false} /> - + ["cards"]} + /> ["listItems"] + } /> diff --git a/apps/web/src/app/[locale]/developers/defi/developers-defi.tsx b/apps/web/src/app/[locale]/developers/defi/developers-defi.tsx index ee8cae235c..2b4e7ee95a 100644 --- a/apps/web/src/app/[locale]/developers/defi/developers-defi.tsx +++ b/apps/web/src/app/[locale]/developers/defi/developers-defi.tsx @@ -128,7 +128,11 @@ export function DevelopersDefiPage() { eyebrow={t("switchback.eyebrow")} headline={t("switchback.headline")} body={t.raw("switchback.body")} - buttons={switchbackButtons as any} + buttons={ + switchbackButtons as React.ComponentProps< + typeof Switchback + >["buttons"] + } /> @@ -140,7 +144,12 @@ export function DevelopersDefiPage() { - + ["cards"] + } + numCols={CARD_DECK_COLUMNS} + /> @@ -148,7 +157,11 @@ export function DevelopersDefiPage() { variant={CONVERSION_PANEL_PRIMARY.variant as "centered"} heading={t("conversionPanel.heading")} body={t("conversionPanel.body")} - buttons={conversionPanelButtons as any} + buttons={ + conversionPanelButtons as React.ComponentProps< + typeof ConversionPanel + >["buttons"] + } logos={[]} showLogos={false} /> @@ -157,7 +170,11 @@ export function DevelopersDefiPage() { ["cards"] + } /> @@ -169,7 +186,11 @@ export function DevelopersDefiPage() { buttons={[]} logos={[]} showLogos={CONVERSION_PANEL_COMMUNITY.showLogos} - listItems={communityListItems as any} + listItems={ + communityListItems as React.ComponentProps< + typeof ConversionPanel + >["listItems"] + } /> diff --git a/apps/web/src/app/[locale]/developers/evm-to-svm/erc4626/developers-evm-to-svm-erc4626.tsx b/apps/web/src/app/[locale]/developers/evm-to-svm/erc4626/developers-evm-to-svm-erc4626.tsx index 6512f23556..8d690e06f8 100644 --- a/apps/web/src/app/[locale]/developers/evm-to-svm/erc4626/developers-evm-to-svm-erc4626.tsx +++ b/apps/web/src/app/[locale]/developers/evm-to-svm/erc4626/developers-evm-to-svm-erc4626.tsx @@ -100,7 +100,7 @@ export function DevelopersEvmToSvmErc4626Page() { {t(row.objectKey)} - {"purposeParts" in row ? ( + {"purposeParts" in row && row.purposeParts ? ( row.purposeParts.map((part, partIndex) => { if (part.type === "code") { return ( @@ -116,9 +116,9 @@ export function DevelopersEvmToSvmErc4626Page() { ); }) - ) : ( + ) : "purposeKey" in row ? ( {t(row.purposeKey)} - )} + ) : null} ))} diff --git a/apps/web/src/app/[locale]/developers/gaming/developers-gaming.tsx b/apps/web/src/app/[locale]/developers/gaming/developers-gaming.tsx index 9d156c1bd1..53cbbbb8dc 100644 --- a/apps/web/src/app/[locale]/developers/gaming/developers-gaming.tsx +++ b/apps/web/src/app/[locale]/developers/gaming/developers-gaming.tsx @@ -24,8 +24,6 @@ import { } from "@/data/developers/gaming"; import { useTranslations } from "next-intl"; -const FeatureHighlightAny = FeatureHighlight as any; - export function DevelopersGamingPage() { const t = useTranslations("developers-gaming"); const blockSpacing = { large: { marginTop: "20px" } }; @@ -40,11 +38,13 @@ export function DevelopersGamingPage() { label: t(`conversionPanels.gamesKit.listItems.${index}`), })); - const featureHighlightCards = FEATURE_HIGHLIGHT_CARDS.map((card, index) => ({ - ...card, - feature: t(`featureHighlight.cards.${index}.feature`), - body: t(`featureHighlight.cards.${index}.body`), - })); + const featureHighlightCards = FEATURE_HIGHLIGHT_CARDS.map( + ({ ...card }, index) => ({ + ...card, + feature: t(`featureHighlight.cards.${index}.feature`), + body: t(`featureHighlight.cards.${index}.body`), + }), + ); const featureHighlightButtons = FEATURE_HIGHLIGHT.buttons.map( (button, index) => ({ @@ -124,15 +124,16 @@ export function DevelopersGamingPage() { - false} /> @@ -173,7 +174,11 @@ export function DevelopersGamingPage() { ["switchbacks"] + } /> @@ -191,7 +196,11 @@ export function DevelopersGamingPage() { variant={FUNDING_PANEL.variant as "centered"} heading={t("conversionPanels.funding.heading")} body={t("conversionPanels.funding.body")} - buttons={fundingButtons as any} + buttons={ + fundingButtons as React.ComponentProps< + typeof ConversionPanel + >["buttons"] + } logos={FUNDING_PANEL.logos} listItems={[]} showLogos={FUNDING_PANEL.showLogos} @@ -205,7 +214,11 @@ export function DevelopersGamingPage() { body={t("conversionPanels.community.body")} buttons={[]} logos={[]} - listItems={communityListItems as any} + listItems={ + communityListItems as React.ComponentProps< + typeof ConversionPanel + >["listItems"] + } showLogos={COMMUNITY_PANEL.showLogos} /> diff --git a/apps/web/src/app/[locale]/developers/nfts/developers-nfts.tsx b/apps/web/src/app/[locale]/developers/nfts/developers-nfts.tsx index 7b088a4e88..46f70046ef 100644 --- a/apps/web/src/app/[locale]/developers/nfts/developers-nfts.tsx +++ b/apps/web/src/app/[locale]/developers/nfts/developers-nfts.tsx @@ -157,9 +157,10 @@ export function DevelopersNftsPage() { headline={t("featureHighlight.headline")} body={t("featureHighlight.body")} desktopBackground={FEATURE_HIGHLIGHT.desktopBackground} - cards={featureHighlightCards as any} - buttons={featureHighlightButtons as any} - valueOf={null} + cards={featureHighlightCards} + buttons={featureHighlightButtons} + // Check if it exists in @solana-foundation/solana-lib after the upstream fix. + valueOf={() => false} /> ({ - ...card, - feature: t(`featureHighlight.cards.${index}.feature`), - body: t(`featureHighlight.cards.${index}.body`), - })); + const featureHighlightCards = FEATURE_HIGHLIGHT_CARDS.map( + ({ ...card }, index) => ({ + ...card, + feature: t(`featureHighlight.cards.${index}.feature`), + body: t(`featureHighlight.cards.${index}.body`), + }), + ); const switchbackButtons = SWITCHBACK_BUTTONS.map((button, index) => ({ ...button, label: t(`switchback.buttons.${index}`), @@ -106,9 +108,10 @@ export function DevelopersPaymentsPage() { body={t("featureHighlight.body")} headingAs={"h2"} desktopBackground={FEATURE_HIGHLIGHT.desktopBackground} - cards={featureHighlightCards as any} + cards={featureHighlightCards} buttons={[]} - valueOf={null} + // Check if it exists in @solana-foundation/solana-lib after the upstream fix. + valueOf={() => false} /> diff --git a/apps/web/src/app/[locale]/events/archive/events-archive.tsx b/apps/web/src/app/[locale]/events/archive/events-archive.tsx index 9095f9ed0a..0bc0bfc23d 100644 --- a/apps/web/src/app/[locale]/events/archive/events-archive.tsx +++ b/apps/web/src/app/[locale]/events/archive/events-archive.tsx @@ -4,9 +4,10 @@ import { StrictMode, useState } from "react"; import EventsHeroSection from "@/components/events/EventsHeroSection"; import EventsList from "@/components/events/EventsList"; import EventsArchivePagination from "@/components/events/archive/EventsArchivePagination"; +import type { CalendarEvent } from "@/lib/events/fetchCalendarEvents"; interface EventsArchivePageProps { - events: any[]; + events: CalendarEvent[]; } export function EventsArchivePage({ events }: EventsArchivePageProps) { diff --git a/apps/web/src/app/[locale]/events/archive/page.tsx b/apps/web/src/app/[locale]/events/archive/page.tsx index 64c61d5afa..48c4bf3962 100644 --- a/apps/web/src/app/[locale]/events/archive/page.tsx +++ b/apps/web/src/app/[locale]/events/archive/page.tsx @@ -33,7 +33,10 @@ export default async function Page(_props: Props) { limit: 20, }); - const sortInstructions = [[(x: any) => x.schedule.from], ["desc"]]; + const sortInstructions = [ + [(x: { schedule: { from: string | null } }) => x.schedule.from], + ["desc"], + ]; const sorted = orderBy( [...mainEvents, ...hhEvents, ...communityEvents, ...communityRiverEvents], ...sortInstructions, diff --git a/apps/web/src/app/[locale]/events/events.tsx b/apps/web/src/app/[locale]/events/events.tsx index cb9d92b1b2..64a9977c29 100644 --- a/apps/web/src/app/[locale]/events/events.tsx +++ b/apps/web/src/app/[locale]/events/events.tsx @@ -8,12 +8,13 @@ import EventsList from "@/components/events/EventsList"; import Button from "@/components/shared/Button"; import Divider from "@/components/shared/Divider"; import { InlineLink } from "@/utils/Link"; +import type { CalendarEvent } from "@/lib/events/fetchCalendarEvents"; interface EventsLandingPageProps { - events: any[]; - communityEvents: any[]; - featuredEvent: any; - usEvents: any[]; + events: CalendarEvent[]; + communityEvents: CalendarEvent[]; + featuredEvent: CalendarEvent | null; + usEvents: CalendarEvent[]; translations: { usHeading: string; usDescription: string; diff --git a/apps/web/src/app/[locale]/events/page.tsx b/apps/web/src/app/[locale]/events/page.tsx index 5f466ce539..70067d72b1 100644 --- a/apps/web/src/app/[locale]/events/page.tsx +++ b/apps/web/src/app/[locale]/events/page.tsx @@ -40,7 +40,10 @@ export default async function Page(_props: Props) { limit: 20, }); - const sortInstructions = [[(x: any) => x.schedule.from], ["asc"]]; + const sortInstructions = [ + [(x: { schedule: { from: string | null } }) => x.schedule.from], + ["asc"], + ]; // sorted and unique main events const sorted = orderBy([...mainEvents], ...sortInstructions); @@ -54,7 +57,7 @@ export default async function Page(_props: Props) { // Set featured event: prefer explicitly marked featured, else first by date const featuredEvent = - unique.find((e: any) => e.featured === true) || unique[0] || null; + unique.find((e) => e.featured === true) || unique[0] || null; const events = [...unique]; const t = await getTranslations(); diff --git a/apps/web/src/app/[locale]/home.tsx b/apps/web/src/app/[locale]/home.tsx index c6f996e7a6..cc4c19aa0c 100644 --- a/apps/web/src/app/[locale]/home.tsx +++ b/apps/web/src/app/[locale]/home.tsx @@ -76,9 +76,7 @@ interface HomePageProps { communitySubtitle: string; communityLinksTranslations: { title: string; description: string }[]; }; - events: (Omit & { - schedule: CalendarEvent["schedule"] & { from: string; to: string }; - })[]; + events: CalendarEvent[]; firstFeaturedEventIndex: number; videos: YouTubePlaylistItem[]; news: PostItem[]; @@ -130,8 +128,8 @@ export function HomePage({ return eventDate >= oneWeekAgo; }) .sort((a, b) => { - const dateA = new Date(a.schedule.from).getTime(); - const dateB = new Date(b.schedule.from).getTime(); + const dateA = new Date(a.schedule.from ?? 0).getTime(); + const dateB = new Date(b.schedule.from ?? 0).getTime(); return dateA - dateB; }); }, [events]); @@ -211,8 +209,8 @@ export function HomePage({ key={event.key} imageSrc={event.img.primary || defaultImg.src} title={event.title} - date={event.schedule.from} - location={event.venue.city || event.venue.address} + date={event.schedule.from ?? undefined} + location={event.venue.city ?? event.venue.address ?? undefined} href={event.rsvp} className="px-1" /> @@ -278,7 +276,7 @@ export function HomePage({ ( {/* Animation styles */} + {/* eslint-disable-next-line react/no-unknown-property*/}