diff --git a/.gitignore b/.gitignore index 68503cd..d55e5e4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ /public/aliases.json /public/topics-data.json /public/speaker-data.json +/public/source-count-data.json +/public/sources-data.json # misc .DS_Store diff --git a/.gitmodules b/.gitmodules index 3c9eb36..dac4e11 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,4 +4,4 @@ [submodule "public/gh-pages"] path = public/gh-pages url = https://github.com/bitcointranscripts/bitcointranscripts.github.io.git - branch = gh-pages \ No newline at end of file + branch = gh-pages diff --git a/contentlayer.config.ts b/contentlayer.config.ts index d9aa52a..1b927a8 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -1,13 +1,8 @@ -import { createSlug, SpeakerData, TopicsData } from "./src/utils"; -import { - defineDocumentType, - defineNestedType, - makeSource, -} from "contentlayer2/source-files"; -import { writeFileSync } from "fs"; import path from "path"; import * as fs from "fs"; -import { Transcript as ContentTranscriptType } from "./.contentlayer/generated/types"; +import { createSlug, createText, SpeakerData, TopicsData, unsluggify } from "./src/utils"; +import { defineDocumentType, defineNestedType, makeSource } from "contentlayer2/source-files"; +import { Transcript as ContentTranscriptType, Source as ContentSourceType } from "./.contentlayer/generated/types"; const Resources = defineNestedType(() => ({ name: "Resources", @@ -16,7 +11,6 @@ const Resources = defineNestedType(() => ({ url: { type: "string" }, }, })); - export interface CategoryInfo { title: string; slug: string; @@ -32,6 +26,10 @@ interface TagInfo { count: number; } +interface ContentTree { + [key: string]: ContentTree | ContentTranscriptType[]; +} + /** * Count the occurrences of all tags across transcripts and write to json file */ @@ -65,7 +63,7 @@ const getTranscriptAliases = (allTranscripts: ContentTranscriptType[]) => { } } - writeFileSync("./public/aliases.json", JSON.stringify(aliases)); + fs.writeFileSync("./public/aliases.json", JSON.stringify(aliases)); }; const getCategories = () => { @@ -96,11 +94,7 @@ function organizeTags(transcripts: ContentTranscriptType[]) { }); // Process all tags at once - const allTags = new Set( - transcripts.flatMap( - (transcript) => transcript.tags?.map((tag) => tag) || [] - ) - ); + const allTags = new Set(transcripts.flatMap((transcript) => transcript.tags?.map((tag) => tag) || [])); allTags.forEach((tag) => { const catInfo = categoryMap.get(tag); @@ -122,13 +116,11 @@ function organizeTags(transcripts: ContentTranscriptType[]) { // Add "Miscellaneous" category with remaining uncategorized tags if (tagsWithoutCategory.size > 0) { - tagsByCategory["Miscellaneous"] = Array.from(tagsWithoutCategory).map( - (tag) => ({ - name: tag, - slug: tag, - count: tagCounts[tag] || 0, - }) - ); + tagsByCategory["Miscellaneous"] = Array.from(tagsWithoutCategory).map((tag) => ({ + name: tag, + slug: tag, + count: tagCounts[tag] || 0, + })); } // Sort tags alphabetically within each category @@ -136,7 +128,7 @@ function organizeTags(transcripts: ContentTranscriptType[]) { tagsByCategory[category].sort((a, b) => a.name.localeCompare(b.name)); }); - writeFileSync("./public/tag-data.json", JSON.stringify(tagsByCategory)); + fs.writeFileSync("./public/tag-data.json", JSON.stringify(tagsByCategory)); return { tagsByCategory, tagsWithoutCategory }; } @@ -146,11 +138,11 @@ function organizeTopics(transcripts: ContentTranscriptType[]) { transcripts.forEach((transcript) => { const slugTags = transcript.tags?.map((tag) => ({ - slug:createSlug(tag), - name:tag + slug: createSlug(tag), + name: tag, })); - slugTags?.forEach(({slug, name}) => { + slugTags?.forEach(({ slug, name }) => { if (slugTopics[slug] !== undefined) { const index = slugTopics[slug]; topicsArray[index].count += 1; @@ -166,44 +158,8 @@ function organizeTopics(transcripts: ContentTranscriptType[]) { }); }); - writeFileSync("./public/topics-data.json", JSON.stringify(topicsArray)); + fs.writeFileSync("./public/topics-data.json", JSON.stringify(topicsArray)); } -/** - * Count the occurrences of all types across transcripts and write to json file - */ -const createTypesCount = (allTranscripts: ContentTranscriptType[]) => { - const typesAndCount: Record = {}; - const relevantTypes = [ - "video", - "core-dev-tech", - "podcast", - "conference", - "meeting", - "club", - "meetup", - "hackathon", - "workshop", - "residency", - "developer-tools", - ]; - - allTranscripts.forEach((transcript) => { - if (transcript.categories) { - transcript.categories.forEach((type: string) => { - const formattedType = createSlug(type); - if (relevantTypes.includes(formattedType)) { - if (formattedType in typesAndCount) { - typesAndCount[formattedType] += 1; - } else { - typesAndCount[formattedType] = 1; - } - } - }); - } - }); - - writeFileSync("./public/types-data.json", JSON.stringify(typesAndCount)); -}; function createSpeakers(transcripts: ContentTranscriptType[]) { const slugSpeakers: any = {}; @@ -211,11 +167,11 @@ function createSpeakers(transcripts: ContentTranscriptType[]) { transcripts.forEach((transcript) => { const slugSpeakersArray = transcript.speakers?.map((speaker) => ({ - slug:createSlug(speaker), + slug: createSlug(speaker), name: speaker, })); - slugSpeakersArray?.forEach(({slug, name}) => { + slugSpeakersArray?.forEach(({ slug, name }) => { if (slugSpeakers[slug] !== undefined) { const index = slugSpeakers[slug]; speakerArray[index].count += 1; @@ -231,8 +187,95 @@ function createSpeakers(transcripts: ContentTranscriptType[]) { }); }); + fs.writeFileSync("./public/speaker-data.json", JSON.stringify(speakerArray)); +} + +function generateSourcesCount(transcripts: ContentTranscriptType[], sources: ContentSourceType[]) { + const sourcesArray: TagInfo[] = []; + const slugSources: Record = {}; + + transcripts.forEach((transcript) => { + const slug = transcript._raw.flattenedPath.split("/")[0]; - writeFileSync("./public/speaker-data.json", JSON.stringify(speakerArray)); + if (slugSources[slug] !== undefined) { + sourcesArray[slugSources[slug]].count += 1; + } else { + const sourcesLength = sourcesArray.length; + slugSources[slug] = sourcesLength; + + const getSourceName = (slug: string) => + sources.find((source) => source.language === "en" && source.slugAsParams[0] === slug)?.title ?? unsluggify(slug); + + sourcesArray[sourcesLength] = { + slug, + name: getSourceName(slug), + count: 1, + }; + } + }); + + fs.writeFileSync("./public/source-count-data.json", JSON.stringify(sourcesArray)); + return { sourcesArray, slugSources }; +} + +const createTypesCount = (transcripts: ContentTranscriptType[], sources: ContentSourceType[]) => { + const { sourcesArray, slugSources } = generateSourcesCount(transcripts, sources); + const nestedTypes: any = {}; + + sources.forEach((transcript) => { + if (transcript.types) { + transcript.types.forEach((type) => { + const slugType = type.charAt(0).toUpperCase() + type.slice(1); + const slug = transcript.slugAsParams[0]; + + const sourceIndex = slugSources[slug]; + const getSource = sourcesArray[sourceIndex] ?? null; + + if (!nestedTypes[slugType]) { + nestedTypes[slugType] = []; + } else { + if (nestedTypes[slugType].includes(getSource) || getSource === null) return; + nestedTypes[slugType].push(getSource); + } + }); + } + }); + + fs.writeFileSync("./public/types-data.json", JSON.stringify(nestedTypes)); +}; + +function organizeContent(transcripts: ContentTranscriptType[]) { + const tree: ContentTree = {}; + + transcripts.forEach((transcript) => { + const parts = transcript.slugAsParams; + let current = tree; + + const isNonEnglishDir = /\w+\.[a-z]{2}\b/.test(parts[parts.length - 1]); + if (isNonEnglishDir) return; + + for (let i = 0; i < parts.length - 1; i++) { + if (!current[parts[i]]) { + current[parts[i]] = i === parts.length - 2 ? [] : {}; + } + current = current[parts[i]] as ContentTree; + } + + (current as unknown as any[]).push({ + title: transcript.title, + speakers: transcript.speakers, + date: transcript.date, + tags: transcript.tags, + sourceFilePath: transcript._raw.sourceFilePath, + flattenedPath: transcript._raw.flattenedPath, + summary: transcript.summary, + body: createText(transcript.body), + source: transcript.source, + }); + }); + + // Save the result as JSON + fs.writeFileSync("./public/sources-data.json", JSON.stringify(tree, null, 2)); } export const Transcript = defineDocumentType(() => ({ @@ -261,6 +304,8 @@ export const Transcript = defineDocumentType(() => ({ aditional_resources: { type: "list", of: Resources }, additional_resources: { type: "list", of: Resources }, weight: { type: "number" }, + types: { type: "list", of: { type: "string" } }, + source_file: { type: "string" }, }, computedFields: { url: { @@ -268,15 +313,49 @@ export const Transcript = defineDocumentType(() => ({ resolve: (doc) => `/${doc._raw.flattenedPath}`, }, slugAsParams: { - type: "string", + type: "list", resolve: (doc) => doc._raw.flattenedPath.split("/"), }, }, })); +export const Source = defineDocumentType(() => ({ + name: "Source", + filePathPattern: `**/_index{,.??}.md`, + contentType: "markdown", + fields: { + title: { type: "string", required: true }, + source: { type: "string" }, + transcription_coverage: { type: "string" }, + hosts: { type: "list", of: { type: "string" } }, + weight: { type: "number" }, + website: { type: "string" }, + types: { type: "list", of: { type: "string" } }, + additional_resources: { type: "list", of: Resources }, + }, + computedFields: { + url: { + type: "string", + resolve: (doc) => `/${doc._raw.flattenedPath.split("/").slice(0, -1).join("/")}`, + }, + language: { + type: "string", + resolve: (doc) => { + const index = doc._raw.flattenedPath.split("/").pop(); + const lan = index?.split(".").length === 2 ? index?.split(".")[1] : "en"; + return lan; + }, + }, + slugAsParams: { + type: "list", + resolve: (doc) => doc._raw.flattenedPath.split("/").slice(0, -1), + }, + }, +})); + export default makeSource({ contentDirPath: path.join(process.cwd(), "public", "bitcoin-transcript"), - documentTypes: [Transcript], + documentTypes: [Source, Transcript], contentDirExclude: [ ".github", ".gitignore", @@ -288,11 +367,13 @@ export default makeSource({ "2018-08-17-richard-bondi-bitcoin-cli-regtest.es.md", ], onSuccess: async (importData) => { - const { allDocuments } = await importData(); - organizeTags(allDocuments); - createTypesCount(allDocuments); - organizeTopics(allDocuments); - getTranscriptAliases(allDocuments); - createSpeakers(allDocuments); + const { allTranscripts, allSources } = await importData(); + organizeTags(allTranscripts); + createTypesCount(allTranscripts, allSources); + organizeTopics(allTranscripts); + getTranscriptAliases(allTranscripts); + createSpeakers(allTranscripts); + generateSourcesCount(allTranscripts, allSources); + organizeContent(allTranscripts); }, }); diff --git a/next.config.mjs b/next.config.mjs index 6f15486..f3b54d8 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,8 +5,8 @@ const nextConfig = { return { fallback: [ { - source: '/:path*.:ext([^/]+)', // intercept all paths ending with a file extension - destination: '/gh-pages/:path*.:ext', // rewrite to gh-pages/[path_here].ext + source: "/:path*.:ext([^/]+)", // intercept all paths ending with a file extension + destination: "/gh-pages/:path*.:ext", // rewrite to gh-pages/[path_here].ext }, { source: "/transcripts", @@ -20,8 +20,12 @@ const nextConfig = { source: "/:path*", destination: "/gh-pages/:path*/index.html", }, - ] - } + { + source: "/sources/:path((?!.*\\.[^/]+).*)", // Matches /source/[any path without a file extension] + destination: "/[...slug]/:path*", // Replace with your catch-all route + }, + ], + }; }, }; diff --git a/public/images/not-found-img.png b/public/images/not-found-img.png new file mode 100644 index 0000000..b7ebbb0 Binary files /dev/null and b/public/images/not-found-img.png differ diff --git a/public/svgs/date-icon.svg b/public/svgs/date-icon.svg new file mode 100644 index 0000000..ef03eca --- /dev/null +++ b/public/svgs/date-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/svgs/link-icon.svg b/public/svgs/link-icon.svg new file mode 100644 index 0000000..a682520 --- /dev/null +++ b/public/svgs/link-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/svgs/tags-icon.svg b/public/svgs/tags-icon.svg new file mode 100644 index 0000000..a53bb62 --- /dev/null +++ b/public/svgs/tags-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/svgs/world-icon.svg b/public/svgs/world-icon.svg new file mode 100644 index 0000000..5b3a70c --- /dev/null +++ b/public/svgs/world-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/(explore)/[...slug]/page.tsx b/src/app/(explore)/[...slug]/page.tsx new file mode 100644 index 0000000..90ed7ef --- /dev/null +++ b/src/app/(explore)/[...slug]/page.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { notFound } from "next/navigation"; +import { ContentTreeArray } from "@/utils/data"; +import LinkIcon from "/public/svgs/link-icon.svg"; +import WorldIcon from "/public/svgs/world-icon.svg"; +import allSources from "@/public/sources-data.json"; +import { ContentTree, filterOutIndexes } from "@/utils"; +import BreadCrumbs from "@/components/common/BreadCrumbs"; +import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons"; +import { allSources as allContentSources } from "contentlayer/generated"; +import TranscriptDetailsCard from "@/components/common/TranscriptDetailsCard"; + +// forces 404 for paths not generated from `generateStaticParams` function. +export const dynamicParams = false; + +export function generateStaticParams() { + return allContentSources.map(({ slugAsParams }) => ({ slug: slugAsParams })); +} + +const page = ({ params }: { params: { slug: string[] } }) => { + const slug = params.slug ?? []; + const contentTree = allSources; + + let current: any = contentTree; + + for (const part of slug) { + if (typeof current === "object" && !Array.isArray(current) && part in current) { + current = current[part] as ContentTree | ContentTreeArray[]; + } else { + notFound(); + } + } + + const displayCurrent = filterOutIndexes(current); + + const pageDetails = allContentSources.find((source) => { + return source.slugAsParams.join("/") === slug.join("/") && source.language === "en"; + }); + + const isDirectoryList = Array.isArray(current); + + return ( +
+
+
+ +
+ + +

Back

+ + +

{pageDetails?.title ?? slug[slug.length - 1]}

+ {isDirectoryList && pageDetails?.website ? ( +
+ world icon + + {pageDetails.website ?? ""} + +
+ ) : null} + + {isDirectoryList && pageDetails?.additional_resources ? ( +
+ link icon +
+ {pageDetails.additional_resources.map((resource, index) => ( + + {resource.title} + + ))} +
+
+ ) : null} +
+
+ + {isDirectoryList ? ( +
+ {(displayCurrent as ContentTreeArray[]) + .sort((a, b) => new Date(b.date!).getTime() - new Date(a.date!).getTime() || a.title.localeCompare(b.title)) + .map((item, i) => ( + + ))} +
+ ) : ( +
+
+ {(displayCurrent as string[]).map((key, i) => ( + + {key} + + ))} +
+
+ )} +
+
+
+ ); +}; + +export default page; diff --git a/src/app/(explore)/layout.tsx b/src/app/(explore)/layout.tsx deleted file mode 100644 index 87b0d99..0000000 --- a/src/app/(explore)/layout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import ExploreNavigation from "@/components/explore/ExploreNavigation"; - -export default function ExploreLayout({ - children, // will be a page or nested layout -}: { - children: React.ReactNode; -}) { - return ( -
-
- -
{children}
- - {/* Include shared UI here e.g. a header or sidebar */} -
-
- ); -} diff --git a/src/app/(explore)/sources/page.tsx b/src/app/(explore)/sources/page.tsx new file mode 100644 index 0000000..cc8ad9d --- /dev/null +++ b/src/app/(explore)/sources/page.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import TranscriptContentPage from "@/components/explore/TranscriptContentPage"; +import allSources from "@/public/source-count-data.json"; + +const SourcesPage = () => { + return ( +
+ +
+ ); +}; + +export default SourcesPage; diff --git a/src/app/(explore)/template.tsx b/src/app/(explore)/template.tsx new file mode 100644 index 0000000..73ce3c6 --- /dev/null +++ b/src/app/(explore)/template.tsx @@ -0,0 +1,23 @@ +import ExploreNavigation from "@/components/explore/ExploreNavigation"; +import FooterComponent from "@/components/layout/FooterComponent"; +import Wrapper from "@/components/layout/Wrapper"; + +export default function ExploreLayout({ + children, // will be a page or nested layout +}: { + children: React.ReactNode; +}) { + return ( + <> +
+ + +
{children}
+ + {/* Include shared UI here e.g. a header or sidebar */} +
+ +
+ + ); +} diff --git a/src/app/(explore)/types/page.tsx b/src/app/(explore)/types/page.tsx new file mode 100644 index 0000000..c61cd31 --- /dev/null +++ b/src/app/(explore)/types/page.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import TranscriptContentPage from "@/components/explore/TranscriptContentPage"; +import allTypesData from "@/public/types-data.json"; + +const CategoriesPage = () => { + return ( +
+ +
+ ); +}; + +export default CategoriesPage; diff --git a/src/app/globals.css b/src/app/globals.css index b497b56..f5fbf76 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -32,7 +32,7 @@ --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; --basic-mono-font: basic-mono; - --header-height: 100px; + --header-height: 86px; } /* @media (prefers-color-scheme: dark) { @@ -49,6 +49,11 @@ body { color: rgb(var(--foreground-rgb)); } */ +@media (min-width: 768px) { + :root { + --header-height: 100px; + } +} @layer utilities { .text-balance { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f2f3e1c..3b8a60c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -24,7 +24,7 @@ export default function RootLayout({
-
{children}
+ {children} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..ccc3220 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import ErrorPageSkeleton from "@/components/common/ErrorPageSkeleton"; + +export default function NotFound() { + return ( + +
+

+ 404 +

+
+

Page not found

+

+ But maybe we found a new Transcript Reviewer? (we mean you!) +
+ (We mean you!) +

+
+
+
+ ); +} diff --git a/src/components/common/BreadCrumbs.tsx b/src/components/common/BreadCrumbs.tsx index 9dd6e45..cd0645c 100644 --- a/src/components/common/BreadCrumbs.tsx +++ b/src/components/common/BreadCrumbs.tsx @@ -1,29 +1,47 @@ +"use client"; + import Link from "next/link"; -import { useParams, usePathname } from "next/navigation"; +import { usePathname } from "next/navigation"; import React from "react"; +import { ExploreNavigationItems } from "@/utils/data"; const BreadCrumbs = () => { - const pathname = usePathname() - const allRoutes= pathname.split("/").map(path=> ({name:path || "home", link:`/${path||""}`})) + const pathname = usePathname(); + + const navListWithoutSources = ExploreNavigationItems.filter((item) => item.href !== "/sources").map((item) => item.href.slice(1)); + + const pathnameArray = pathname.split("/"); + const isNotSourcesPage = navListWithoutSources.includes(pathnameArray[1]); + + const allRoutes = pathnameArray.map((path, idx) => { + const route = pathname + .split("/") + .slice(0, idx + 1) + .join("/"); + return { name: path || "home", link: route || "/" }; + }); + + if (!isNotSourcesPage && pathnameArray[1] !== "sources") { + allRoutes.splice(1, 0, { name: "Sources", link: "/sources" }); + } + + const isActive = allRoutes[allRoutes.length - 1]; return ( -
- { - allRoutes.map((link, i) => ( -
- - {link.name} - - {i !== allRoutes.length - 1 && ( -

/

- )} -
- ))} +
+ {allRoutes.map((link, i) => ( +
+ + {link.name} + + {i !== allRoutes.length - 1 &&

/

} +
+ ))}
); }; diff --git a/src/components/common/ErrorPageSkeleton.tsx b/src/components/common/ErrorPageSkeleton.tsx new file mode 100644 index 0000000..7ef6c1c --- /dev/null +++ b/src/components/common/ErrorPageSkeleton.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons"; + +const ErrorPageSkeleton = ({ children }: { children: React.ReactNode }) => { + return ( +
+
+
{children}
+
+ not found image +
+ +
+ + + Return Home + + + Review Transcripts + + +
+
+
+ ); +}; + +export default ErrorPageSkeleton; diff --git a/src/components/common/NoSearchResult.tsx b/src/components/common/NoSearchResult.tsx new file mode 100644 index 0000000..956fe0e --- /dev/null +++ b/src/components/common/NoSearchResult.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import ErrorPageSkeleton from "./ErrorPageSkeleton"; + +const NoSearchResult = () => { + return ( + +
+

+ No Search Results Found +

+
+

Help us review transcripts to make sure this never happens again!

+
+
+
+ ); +}; + +export default NoSearchResult; diff --git a/src/components/common/TranscriptDetailsCard.tsx b/src/components/common/TranscriptDetailsCard.tsx new file mode 100644 index 0000000..e11d10a --- /dev/null +++ b/src/components/common/TranscriptDetailsCard.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { ContentTreeArray } from "@/utils/data"; +import DateIcon from "/public/svgs/date-icon.svg"; +import TagsIcon from "/public/svgs/tags-icon.svg"; +import { createSlug, formatDate, unsluggify } from "@/utils"; +import { MicIcon } from "@bitcoin-dev-project/bdp-ui/icons"; + +const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: string[] }) => { + const { speakers, tags, summary, date, title, body, flattenedPath: url } = data; + + const calculateRemaining = (data: string[]) => (data?.length && data.length > 3 ? data.length - 3 : 0); + + return ( +
+
+
+
+
+ {slug + .join(" / ") + .split(" ") + .map((slg, i) => ( +

+ {unsluggify(slg)} +

+ ))} +
+ + {date && ( +
+ date icon +

{formatDate(date!)}

+
+ )} +
+ + + {title} + +
+
+ +
+ {speakers?.length ? ( +
+ <> + + + +
+
+ {speakers.slice(0, 3).map((speaker, idx) => ( + + {speaker} + + ))} + + {calculateRemaining(speakers) === 0 ? null : ( +

+ + {calculateRemaining(speakers)} more +

+ )} +
+
+ +
+ ) : null} + +
+ {tags?.length ? ( + <> + + date icon + +
+
+ {tags.slice(0, 3).map((tag, idx) => ( + + {unsluggify(tag)} + + ))} + + {calculateRemaining(tags) === 0 ? null : ( +

+ + {calculateRemaining(tags)} more +

+ )} +
+
+ + ) : null} +
+
+ + {summary || body ? ( +
+

{summary ? summary : body}

+
+ ) : null} +
+ ); +}; + +export default TranscriptDetailsCard; diff --git a/src/components/explore/ContentGrouping.tsx b/src/components/explore/ContentGrouping.tsx index 3bd50a6..09d0a76 100644 --- a/src/components/explore/ContentGrouping.tsx +++ b/src/components/explore/ContentGrouping.tsx @@ -1,3 +1,5 @@ +"use client" + import { createSlug, GroupedData } from "@/utils"; import Link from "next/link"; import { useEffect, useRef } from "react"; diff --git a/src/components/explore/ExploreNavigation.tsx b/src/components/explore/ExploreNavigation.tsx index 3751e9c..2c19ab3 100644 --- a/src/components/explore/ExploreNavigation.tsx +++ b/src/components/explore/ExploreNavigation.tsx @@ -7,29 +7,22 @@ import { ArrowLinkUpRight } from "@bitcoin-dev-project/bdp-ui/icons"; const ExploreNavigation = () => { return ( -
-
+
+
{ExploreNavigationItems.map((item) => ( - + ))}
-
-

Review Transcripts

+
+

Review Transcripts

- Earn Sats - - + Earn Sats + +
@@ -37,15 +30,25 @@ const ExploreNavigation = () => { ); }; -const ExploreNavigationItem = ({ - href, - title, -}: { - href: string; - title: string; -}) => { +const ExploreNavigationItem = ({ href, title }: { href: string; title: string }) => { const pathname = usePathname(); - const isActive = pathname === href; + let pagePath = pathname.split("/")[1].toLowerCase(); + + const switchState = () => { + let isActive = false; + const navList = ExploreNavigationItems.map((item) => item.title.toLowerCase()).includes(pagePath); + + if (navList) { + isActive = pagePath === title.toLowerCase(); + } else if (!navList) { + pagePath = "sources"; + isActive = pagePath === title.toLowerCase(); + } + + return { isActive }; + }; + + const { isActive } = switchState(); return ( {title} diff --git a/src/components/explore/GroupedTranscriptContent.tsx b/src/components/explore/GroupedTranscriptContent.tsx index c4ea5a2..8afccdc 100644 --- a/src/components/explore/GroupedTranscriptContent.tsx +++ b/src/components/explore/GroupedTranscriptContent.tsx @@ -38,7 +38,7 @@ const GroupedTranscriptContent = ({ id={topicsByAlphabet[0].toLowerCase()} className="flex flex-col gap-7 " > -

{topicsByAlphabet[0]}

+

{topicsByAlphabet[0]}

{topicsByAlphabet[1].map((topics, i) => ( -

{topicsByAlphabet[0]}

+

{topicsByAlphabet[0]}

{topicsByAlphabet[1] && topicsByAlphabet[1].map((data, i) => ( diff --git a/src/components/explore/SingleTranscriptContent.tsx b/src/components/explore/SingleTranscriptContent.tsx index 4a5599f..bdcce6f 100644 --- a/src/components/explore/SingleTranscriptContent.tsx +++ b/src/components/explore/SingleTranscriptContent.tsx @@ -3,16 +3,18 @@ import Link from "next/link"; import React from "react"; type SingleContent = { - linkName: DepreciatedCategories -} & TopicsData -const SingleTranscriptContent = ({ count, slug, name, linkName }: SingleContent ) => { + linkName: DepreciatedCategories; +} & TopicsData; +const SingleTranscriptContent = ({ count, slug, name, linkName }: SingleContent) => { + const url = linkName === "sources" ? `/${slug}` : `/${linkName}/${slug}`; + return ( - {name} - {getDoubleDigits(count)} + {name} + {getDoubleDigits(count)} ); }; diff --git a/src/components/explore/TranscriptContentPage.tsx b/src/components/explore/TranscriptContentPage.tsx index 95909bb..2d05077 100644 --- a/src/components/explore/TranscriptContentPage.tsx +++ b/src/components/explore/TranscriptContentPage.tsx @@ -1,15 +1,10 @@ -"use client" +"use client"; import React, { FC, useState } from "react"; import BreadCrumbs from "../common/BreadCrumbs"; import GroupedTranscriptContent from "./GroupedTranscriptContent"; import AlphabetGrouping from "./AlphabetGrouping"; -import { - createSlug, - DepreciatedCategories, - groupDataByAlphabet, - sortKeysAlphabetically, -} from "@/utils"; +import { createSlug, DepreciatedCategories, groupDataByAlphabet, sortKeysAlphabetically } from "@/utils"; import MobileAlphabetGrouping from "./MobileAlphabetGrouping"; import ContentGrouping from "./ContentGrouping"; @@ -22,52 +17,27 @@ interface ITranscriptContentPage { linkName: DepreciatedCategories; } -const TranscriptContentPage: FC = ({ - header, - data, - description, - mobileDescription, - linkName, - type, -}) => { - const groupedData = - type === "alphabet" - ? groupDataByAlphabet(data) - : sortKeysAlphabetically(data); - const [currentGroup, setCurrentGroup] = useState( type === "alphabet"? "A": createSlug(Object.keys(groupedData)[0])); +const TranscriptContentPage: FC = ({ header, data, description, mobileDescription, linkName, type }) => { + const groupedData = type === "alphabet" ? groupDataByAlphabet(data) : sortKeysAlphabetically(data); + const [currentGroup, setCurrentGroup] = useState(type === "alphabet" ? "A" : createSlug(Object.keys(groupedData)[0])); return ( -
-
-
- {type == "alphabet" && ( - - )} - {type == "words" && ( - - )} +
+
+
+ {type == "alphabet" && } + {type == "words" && }
-
+
-
-

{header}

-

- {description} -

-

- {mobileDescription || description} -

+
+

{header}

+

{description}

+

{mobileDescription || description}

-
+
{groupedData && type === "alphabet" && Object.entries(groupedData).map((arg, i) => ( @@ -94,23 +64,12 @@ const TranscriptContentPage: FC = ({
-
- {type === "alphabet" && ( - - )} - {type == "words" && ( - - )} +
+ {type === "alphabet" && } + {type == "words" && }
); }; -export default TranscriptContentPage; \ No newline at end of file +export default TranscriptContentPage; diff --git a/src/components/landing-page/TranscriptCard.tsx b/src/components/landing-page/TranscriptCard.tsx index 493ba24..be2226d 100644 --- a/src/components/landing-page/TranscriptCard.tsx +++ b/src/components/landing-page/TranscriptCard.tsx @@ -79,7 +79,7 @@ export const ExploreTranscriptCard = ({ linkUrl = `/tags/#${parseUrl}`; break; case "TYPE": - linkUrl = `/categories/${parseUrl}`; + linkUrl = `/types#${parseUrl}`; break; default: break; diff --git a/src/components/landing-page/explore-transcripts/ExploreTranscriptClient.tsx b/src/components/landing-page/explore-transcripts/ExploreTranscriptClient.tsx index 34cb169..1127a8e 100644 --- a/src/components/landing-page/explore-transcripts/ExploreTranscriptClient.tsx +++ b/src/components/landing-page/explore-transcripts/ExploreTranscriptClient.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { Carousel } from "@bitcoin-dev-project/bdp-ui"; import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons"; import { ExploreTranscriptCard } from "../TranscriptCard"; +import { countItemsAndSort } from "@/utils"; interface TagInfo { name: string; @@ -14,19 +15,12 @@ interface TagInfo { interface ExploreTranscriptClientProps { categories: { [category: string]: TagInfo[] }; - types: { - [key: string]: number; - }; + types: { [category: string]: TagInfo[] }; } const ExploreTranscriptClient = ({ categories, types }: ExploreTranscriptClientProps) => { - const sortedCategories = Object.fromEntries( - Object.entries(categories) - .sort(([a], [b]) => a.localeCompare(b)) - .map(([key, value]) => [key, value.sort((a, b) => a.name.localeCompare(b.name))]) - ); - - const sortedTypes = Object.fromEntries(Object.entries(types).sort(([a], [b]) => a.localeCompare(b))); + const sortedCategories = countItemsAndSort(categories); + const sortedTypes = countItemsAndSort(types); return (
@@ -43,7 +37,7 @@ const ExploreTranscriptClient = ({ categories, types }: ExploreTranscriptClientP {Object.entries(sortedCategories).map(([key, value]) => ( - + ))} diff --git a/src/components/landing-page/explore-transcripts/ExploreTranscripts.tsx b/src/components/landing-page/explore-transcripts/ExploreTranscripts.tsx index de5bffc..440a328 100644 --- a/src/components/landing-page/explore-transcripts/ExploreTranscripts.tsx +++ b/src/components/landing-page/explore-transcripts/ExploreTranscripts.tsx @@ -10,7 +10,7 @@ function getTags() { return JSON.parse(fileContents); } -const getTypes = (): { [key: string]: number } => { +const getTypes = () => { const filePath = path.join(process.cwd(), "public", "types-data.json"); const fileContents = fs.readFileSync(filePath, "utf8"); return JSON.parse(fileContents); diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 9201de6..4aa1a26 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -189,7 +189,9 @@ const Header = () => {
- + + +