From e0d38beb2059ead5bc1d593895da134ef03aaf3a Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 19 Dec 2025 21:44:27 +1100 Subject: [PATCH 1/4] feat: React SDK --- .gitignore | 4 + apps/docs/package.json | 2 +- apps/page/components/footer.tsx | 2 +- apps/page/components/page-header.tsx | 2 +- apps/page/components/post.tsx | 6 +- apps/page/components/reactions.tsx | 2 +- apps/page/components/seo-tags.tsx | 2 +- apps/page/components/subscribe-prompt.tsx | 4 +- apps/page/lib/data.ts | 34 +++++- apps/page/lib/notifications.ts | 4 +- apps/page/lib/url.ts | 2 +- apps/page/lib/visitor-auth.ts | 2 +- apps/page/package.json | 8 +- apps/page/pages/_sites/[site]/index.tsx | 4 +- .../confirm-email-subscription.tsx | 2 +- apps/page/pages/_sites/[site]/plain.tsx | 4 +- .../_sites/[site]/post/[postId]/[slug].tsx | 4 +- .../_sites/[site]/roadmap/[roadmap_slug].tsx | 4 +- .../page/pages/api/auth/request-magic-link.ts | 2 +- apps/page/pages/api/auth/verify-magic-link.ts | 2 +- apps/page/pages/api/json.ts | 93 +++++++++++++--- apps/page/pages/api/latest.ts | 4 +- apps/page/pages/api/markdown.ts | 2 +- apps/page/pages/api/pa/view.ts | 2 +- apps/page/pages/api/pinned.ts | 4 +- apps/page/pages/api/post/[id].ts | 6 +- apps/page/pages/api/post/react.ts | 2 +- apps/page/pages/api/post/reactions.ts | 2 +- apps/page/pages/api/posts.ts | 6 +- apps/page/pages/api/roadmap/submit-triage.ts | 2 +- apps/page/pages/api/roadmap/vote.ts | 2 +- apps/page/pages/api/roadmap/votes.ts | 2 +- apps/page/pages/api/rss.ts | 2 +- apps/page/tailwind.config.js | 2 +- ...expand-concept-prompt-dialog.component.tsx | 2 +- .../ai-prood-read-dialog.component.tsx | 2 +- ...-suggest-title-prompt-dialog.component.tsx | 2 +- .../confirm-delete-dialog.component.tsx | 2 +- .../date-time-prompt-dialog.component.tsx | 2 +- .../dialogs/manage-team-dialog.component.tsx | 2 +- .../dialogs/warning-dialog.component.tsx | 2 +- apps/web/components/entity/empty-state.tsx | 2 +- .../components/forms/page-form.component.tsx | 4 +- .../components/forms/post-form.component.tsx | 6 +- .../layout/blog-layout.component.tsx | 2 +- apps/web/components/marketing/changelog.tsx | 2 +- apps/web/components/marketing/hero.tsx | 7 +- .../page-settings/custom-domain.tsx | 4 +- .../components/page-settings/github-agent.tsx | 4 +- .../components/page-settings/integrations.tsx | 4 +- .../page-settings/notifications.tsx | 4 +- .../components/page-settings/social-links.tsx | 4 +- apps/web/components/page-settings/style.tsx | 6 +- apps/web/components/post/post-options.tsx | 2 +- apps/web/components/post/post-status.tsx | 2 +- apps/web/components/post/post.tsx | 6 +- apps/web/components/roadmap/RoadmapBoard.tsx | 2 +- apps/web/components/roadmap/RoadmapColumn.tsx | 2 +- apps/web/components/roadmap/RoadmapItem.tsx | 2 +- .../components/roadmap/RoadmapItemModal.tsx | 2 +- .../web/components/roadmap/TriageItemCard.tsx | 2 +- apps/web/components/roadmap/TriageRow.tsx | 2 +- .../roadmap/hooks/useRoadmapDragDrop.ts | 2 +- .../roadmap/hooks/useRoadmapItems.ts | 2 +- apps/web/components/roadmap/types.ts | 2 +- apps/web/data/schema.ts | 2 +- apps/web/data/user.interface.ts | 2 +- .../billing/report-pages-usage-invoice.ts | 2 +- .../email/send-roadmap-triage-notification.ts | 2 +- .../inngest/email/send-visitor-magic-link.ts | 2 +- apps/web/inngest/jobs/delete-images.ts | 2 +- .../inngest/jobs/process-github-changelog.ts | 4 +- apps/web/package.json | 9 +- apps/web/pages/account/billing.tsx | 4 +- .../billing/jobs/cleanup-inactive-pages.ts | 4 +- .../pages/api/billing/jobs/report-usage.ts | 4 +- .../api/emails/subscribers/export-csv.ts | 2 +- .../web/pages/api/emails/subscribers/index.ts | 2 +- .../integrations/github/action-new-post.tsx | 4 +- .../api/integrations/github/installations.ts | 4 +- .../pages/api/integrations/github/webhook.ts | 2 +- .../integrations/zapier/action-new-post.tsx | 4 +- .../integrations/zapier/trigger-new-post.tsx | 6 +- apps/web/pages/api/pages/new.ts | 2 +- apps/web/pages/api/pages/reactions.ts | 2 +- apps/web/pages/api/pages/settings/index.ts | 2 +- .../pages/api/pages/settings/remove-domain.ts | 2 +- apps/web/pages/api/pages/settings/webhook.ts | 2 +- apps/web/pages/api/pages/validate-url.ts | 4 +- apps/web/pages/api/pages/webhook.ts | 2 +- apps/web/pages/api/posts/index.ts | 2 +- apps/web/pages/api/posts/webhook.ts | 4 +- apps/web/pages/api/roadmap/triage/delete.ts | 2 +- .../pages/api/roadmap/triage/move-to-board.ts | 2 +- apps/web/pages/api/storage/get-mime-type.ts | 2 +- .../pages/api/teams/invite/accept/index.ts | 2 +- apps/web/pages/api/teams/invite/index.ts | 2 +- apps/web/pages/api/teams/member/[id]/index.ts | 2 +- apps/web/pages/blog/index.tsx | 2 +- apps/web/pages/changelog.tsx | 101 ++++++++++++++++++ .../free-tools/ai-changelog-generator.tsx | 2 +- apps/web/pages/onboarding/open-page.tsx | 4 +- apps/web/pages/pages/[page_id]/[post_id].tsx | 2 +- apps/web/pages/pages/[page_id]/analytics.tsx | 4 +- apps/web/pages/pages/[page_id]/audit-logs.tsx | 6 +- apps/web/pages/pages/[page_id]/edit.tsx | 2 +- apps/web/pages/pages/[page_id]/index.tsx | 4 +- apps/web/pages/pages/[page_id]/new.tsx | 2 +- .../[page_id]/roadmap/[board_id]/settings.tsx | 2 +- .../pages/[page_id]/settings/[activeTab].tsx | 2 +- apps/web/pages/pages/index.tsx | 2 +- apps/web/pages/teams/index.tsx | 4 +- apps/web/tailwind.config.js | 2 +- apps/web/utils/changelog-ai.ts | 2 +- apps/web/utils/email.ts | 6 +- apps/web/utils/hooks/usePage.ts | 2 +- apps/web/utils/hooks/usePageSettings.ts | 2 +- apps/web/utils/hooks/usePageUrl.ts | 2 +- apps/web/utils/hooks/usePosts.ts | 2 +- apps/web/utils/supabase/client.ts | 2 +- apps/web/utils/supabase/middleware.ts | 2 +- apps/web/utils/supabase/server.ts | 2 +- apps/web/utils/supabase/withSupabase.ts | 2 +- apps/web/utils/useDatabase.ts | 8 +- apps/web/utils/useSSR.ts | 2 +- apps/web/utils/useUser.tsx | 2 +- apps/web/utils/withAuth.ts | 4 +- packages/react-sdk/README.md | 86 +++++++++++++++ packages/react-sdk/package.json | 39 +++++++ packages/react-sdk/src/client.ts | 59 ++++++++++ .../src/components/ChangelogPost.tsx | 17 +++ packages/react-sdk/src/components/index.ts | 1 + packages/react-sdk/src/hooks/index.ts | 2 + packages/react-sdk/src/hooks/usePosts.ts | 94 ++++++++++++++++ packages/react-sdk/src/index.ts | 15 +++ packages/react-sdk/src/types.ts | 51 +++++++++ packages/react-sdk/src/utils.ts | 29 +++++ packages/react-sdk/tsconfig.json | 22 ++++ packages/supabase/package.json | 2 +- packages/ui/package.json | 2 +- packages/utils/package.json | 2 +- pnpm-lock.yaml | 83 ++++++++++++-- 142 files changed, 888 insertions(+), 210 deletions(-) create mode 100644 apps/web/pages/changelog.tsx create mode 100644 packages/react-sdk/README.md create mode 100644 packages/react-sdk/package.json create mode 100644 packages/react-sdk/src/client.ts create mode 100644 packages/react-sdk/src/components/ChangelogPost.tsx create mode 100644 packages/react-sdk/src/components/index.ts create mode 100644 packages/react-sdk/src/hooks/index.ts create mode 100644 packages/react-sdk/src/hooks/usePosts.ts create mode 100644 packages/react-sdk/src/index.ts create mode 100644 packages/react-sdk/src/types.ts create mode 100644 packages/react-sdk/src/utils.ts create mode 100644 packages/react-sdk/tsconfig.json diff --git a/.gitignore b/.gitignore index 9e86ef8..ed95d62 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,7 @@ yarn-error.log* # Sentry Auth Token .sentryclirc + +# packages +dist/ +node_modules/ diff --git a/apps/docs/package.json b/apps/docs/package.json index 7ac50e5..f55c12a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/docs", + "name": "@changespage/docs", "version": "1.0.0", "private": true, "scripts": { diff --git a/apps/page/components/footer.tsx b/apps/page/components/footer.tsx index 1000a98..3e1351c 100644 --- a/apps/page/components/footer.tsx +++ b/apps/page/components/footer.tsx @@ -1,4 +1,4 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; +import { IPageSettings } from "@changespage/supabase/types/page"; import Image from "next/image"; import { useEffect } from "react"; import { PageRoadmap } from "../lib/data"; diff --git a/apps/page/components/page-header.tsx b/apps/page/components/page-header.tsx index 7c2e875..a0a51e9 100644 --- a/apps/page/components/page-header.tsx +++ b/apps/page/components/page-header.tsx @@ -1,4 +1,4 @@ -import { IPage, IPageSettings } from "@changes-page/supabase/types/page"; +import { IPage, IPageSettings } from "@changespage/supabase/types/page"; import { Menu } from "@headlessui/react"; import { ChevronDownIcon } from "@heroicons/react/outline"; import classNames from "classnames"; diff --git a/apps/page/components/post.tsx b/apps/page/components/post.tsx index 0a55bda..67d8f67 100644 --- a/apps/page/components/post.tsx +++ b/apps/page/components/post.tsx @@ -1,5 +1,5 @@ -import { PostType } from "@changes-page/supabase/types/page"; -import { PostTypeBadge } from "@changes-page/ui"; +import { PostType } from "@changespage/supabase/types/page"; +import { PostTypeBadge } from "@changespage/ui"; import classNames from "classnames"; import dynamic from "next/dynamic"; import Image from "next/image"; @@ -14,7 +14,7 @@ import { IPostPublicData } from "../lib/data"; import Reactions from "./reactions"; const PostDateTime = dynamic( - () => import("@changes-page/ui").then((mod) => mod.PostDateTime), + () => import("@changespage/ui").then((mod) => mod.PostDateTime), { ssr: false, } diff --git a/apps/page/components/reactions.tsx b/apps/page/components/reactions.tsx index 40c1bf9..b9e923b 100644 --- a/apps/page/components/reactions.tsx +++ b/apps/page/components/reactions.tsx @@ -1,4 +1,4 @@ -import { IReactions } from "@changes-page/supabase/types/page"; +import { IReactions } from "@changespage/supabase/types/page"; import { Transition } from "@headlessui/react"; import classNames from "classnames"; import { useCallback, useEffect, useState } from "react"; diff --git a/apps/page/components/seo-tags.tsx b/apps/page/components/seo-tags.tsx index 8a1db5a..79d3ede 100644 --- a/apps/page/components/seo-tags.tsx +++ b/apps/page/components/seo-tags.tsx @@ -2,7 +2,7 @@ import { IPage, IPageSettings, PageTypeToLabel, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; import { NextSeo } from "next-seo"; import Head from "next/head"; import { useMemo } from "react"; diff --git a/apps/page/components/subscribe-prompt.tsx b/apps/page/components/subscribe-prompt.tsx index 9f2380f..24a6bc2 100644 --- a/apps/page/components/subscribe-prompt.tsx +++ b/apps/page/components/subscribe-prompt.tsx @@ -1,5 +1,5 @@ -import { IPage, IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { IPage, IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import { BellIcon, CheckCircleIcon, diff --git a/apps/page/lib/data.ts b/apps/page/lib/data.ts index 7686d4f..bd93cbf 100644 --- a/apps/page/lib/data.ts +++ b/apps/page/lib/data.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { Database } from "@changes-page/supabase/types"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { Database } from "@changespage/supabase/types"; import { IPage, IPageSettings, @@ -7,7 +7,7 @@ import { IRoadmapBoard, IRoadmapColumn, IRoadmapItem, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; import { sanitizeCss } from "./css"; const PAGINATION_LIMIT = 50; @@ -441,9 +441,37 @@ async function getRoadmapBySlug( return { board, columns, items }; } +async function fetchPostsWithPagination( + pageId: string, + { limit, offset }: { limit?: number; offset?: number } +): Promise<{ posts: IPost[]; postsCount: number }> { + const effectiveLimit = Math.min(limit ?? PAGINATION_LIMIT, PAGINATION_LIMIT); + const effectiveOffset = offset ?? 0; + + const { + data: posts, + count: postsCount, + error: postsError, + } = await supabaseAdmin + .from("posts") + .select(postSelectParams, { count: "exact" }) + .eq("page_id", String(pageId)) + .eq("status", "published") + .range(effectiveOffset, effectiveOffset + effectiveLimit - 1) + .order("publication_date", { ascending: false }); + + if (postsError) { + console.error("Fetch post error", postsError); + throw new Error("Failed to fetch posts"); + } + + return { posts: (posts ?? []) as Array, postsCount: postsCount ?? 0 }; +} + export { fetchPostById, fetchPosts, + fetchPostsWithPagination, fetchRenderData, getRoadmapBySlug, PAGINATION_LIMIT, diff --git a/apps/page/lib/notifications.ts b/apps/page/lib/notifications.ts index 2c7b87e..88d9e78 100644 --- a/apps/page/lib/notifications.ts +++ b/apps/page/lib/notifications.ts @@ -1,6 +1,6 @@ import { v4 } from "uuid"; -import { IPageEmailSubscriber } from "@changes-page/supabase/types/page"; -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { IPageEmailSubscriber } from "@changespage/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; async function subscribeViaEmail( pageId: string, diff --git a/apps/page/lib/url.ts b/apps/page/lib/url.ts index 623b035..894c582 100644 --- a/apps/page/lib/url.ts +++ b/apps/page/lib/url.ts @@ -1,4 +1,4 @@ -import { IPage, IPageSettings } from "@changes-page/supabase/types/page"; +import { IPage, IPageSettings } from "@changespage/supabase/types/page"; import slugify from "slugify"; import { IPostPublicData } from "./data"; diff --git a/apps/page/lib/visitor-auth.ts b/apps/page/lib/visitor-auth.ts index 4ad6fa1..3b124f4 100644 --- a/apps/page/lib/visitor-auth.ts +++ b/apps/page/lib/visitor-auth.ts @@ -1,4 +1,4 @@ -import { IPageVisitor } from "@changes-page/supabase/types/page"; +import { IPageVisitor } from "@changespage/supabase/types/page"; import { SignJWT, jwtVerify } from "jose"; import type { NextApiRequest } from "next"; import { randomBytes, randomUUID } from "node:crypto"; diff --git a/apps/page/package.json b/apps/page/package.json index b109092..217dce6 100644 --- a/apps/page/package.json +++ b/apps/page/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/page", + "name": "@changespage/page", "version": "1.19.0", "private": true, "scripts": { @@ -12,9 +12,9 @@ }, "dependencies": { "@arcjet/next": "1.0.0-alpha.20", - "@changes-page/supabase": "workspace:*", - "@changes-page/ui": "workspace:*", - "@changes-page/utils": "workspace:*", + "@changespage/supabase": "workspace:*", + "@changespage/ui": "workspace:*", + "@changespage/utils": "workspace:*", "@headlessui/react": "^1.4.0", "@heroicons/react": "^1.0.3", "@sentry/nextjs": "^7.93.0", diff --git a/apps/page/pages/_sites/[site]/index.tsx b/apps/page/pages/_sites/[site]/index.tsx index 6f1134b..f662c25 100644 --- a/apps/page/pages/_sites/[site]/index.tsx +++ b/apps/page/pages/_sites/[site]/index.tsx @@ -1,5 +1,5 @@ -import { IPage, IPageSettings, IPost } from "@changes-page/supabase/types/page"; -import { Timeline } from "@changes-page/ui"; +import { IPage, IPageSettings, IPost } from "@changespage/supabase/types/page"; +import { Timeline } from "@changespage/ui"; import classNames from "classnames"; import { useCallback, useMemo, useState } from "react"; import Footer from "../../../components/footer"; diff --git a/apps/page/pages/_sites/[site]/notifications/confirm-email-subscription.tsx b/apps/page/pages/_sites/[site]/notifications/confirm-email-subscription.tsx index 4e2a13f..ac20b9e 100644 --- a/apps/page/pages/_sites/[site]/notifications/confirm-email-subscription.tsx +++ b/apps/page/pages/_sites/[site]/notifications/confirm-email-subscription.tsx @@ -1,4 +1,4 @@ -import { IPage, IPageSettings } from "@changes-page/supabase/types/page"; +import { IPage, IPageSettings } from "@changespage/supabase/types/page"; import { CheckCircleIcon } from "@heroicons/react/outline"; import type { GetServerSideProps } from "next"; import { usePageTheme } from "../../../../hooks/usePageTheme"; diff --git a/apps/page/pages/_sites/[site]/plain.tsx b/apps/page/pages/_sites/[site]/plain.tsx index 1f76920..f3a1391 100644 --- a/apps/page/pages/_sites/[site]/plain.tsx +++ b/apps/page/pages/_sites/[site]/plain.tsx @@ -4,8 +4,8 @@ import { IPost, PostType, PostTypeToLabel, -} from "@changes-page/supabase/types/page"; -import { DateTime } from "@changes-page/utils"; +} from "@changespage/supabase/types/page"; +import { DateTime } from "@changespage/utils"; import { GetServerSidePropsContext } from "next"; import { useRouter } from "next/router"; import { useEffect } from "react"; diff --git a/apps/page/pages/_sites/[site]/post/[postId]/[slug].tsx b/apps/page/pages/_sites/[site]/post/[postId]/[slug].tsx index e2fad9e..b5632e5 100644 --- a/apps/page/pages/_sites/[site]/post/[postId]/[slug].tsx +++ b/apps/page/pages/_sites/[site]/post/[postId]/[slug].tsx @@ -1,5 +1,5 @@ -import { Timeline } from "@changes-page/ui"; -import { convertMarkdownToPlainText } from "@changes-page/utils"; +import { Timeline } from "@changespage/ui"; +import { convertMarkdownToPlainText } from "@changespage/utils"; import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/solid"; import { InferGetServerSidePropsType } from "next"; import Link from "next/link"; diff --git a/apps/page/pages/_sites/[site]/roadmap/[roadmap_slug].tsx b/apps/page/pages/_sites/[site]/roadmap/[roadmap_slug].tsx index 446d413..c962860 100644 --- a/apps/page/pages/_sites/[site]/roadmap/[roadmap_slug].tsx +++ b/apps/page/pages/_sites/[site]/roadmap/[roadmap_slug].tsx @@ -3,8 +3,8 @@ import { IPageSettings, IRoadmapBoard, IRoadmapColumn, -} from "@changes-page/supabase/types/page"; -import { getCategoryColorClasses } from "@changes-page/utils"; +} from "@changespage/supabase/types/page"; +import { getCategoryColorClasses } from "@changespage/utils"; import { Dialog, Transition } from "@headlessui/react"; import { PlusIcon, XIcon } from "@heroicons/react/outline"; import { Fragment, useEffect, useMemo, useState } from "react"; diff --git a/apps/page/pages/api/auth/request-magic-link.ts b/apps/page/pages/api/auth/request-magic-link.ts index 160d6bb..43e36e9 100644 --- a/apps/page/pages/api/auth/request-magic-link.ts +++ b/apps/page/pages/api/auth/request-magic-link.ts @@ -1,5 +1,5 @@ import arcjet, { protectSignup } from "@arcjet/next"; -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import validator from "validator"; import { diff --git a/apps/page/pages/api/auth/verify-magic-link.ts b/apps/page/pages/api/auth/verify-magic-link.ts index 2c5c8fe..917b57a 100644 --- a/apps/page/pages/api/auth/verify-magic-link.ts +++ b/apps/page/pages/api/auth/verify-magic-link.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { createVisitorJWT, isTokenExpired } from "../../../lib/visitor-auth"; diff --git a/apps/page/pages/api/json.ts b/apps/page/pages/api/json.ts index 3625d0c..c481242 100644 --- a/apps/page/pages/api/json.ts +++ b/apps/page/pages/api/json.ts @@ -1,20 +1,25 @@ -import { IPost } from "@changes-page/supabase/types/page"; -import { convertMarkdownToPlainText } from "@changes-page/utils"; +import { IPost } from "@changespage/supabase/types/page"; +import { convertMarkdownToPlainText } from "@changespage/utils"; import type { NextApiRequest, NextApiResponse } from "next"; import { allowCors } from "../../lib/cors"; import { fetchPosts, + fetchPostsWithPagination, fetchRenderData, translateHostToPageIdentifier, } from "../../lib/data"; import { getPageUrl, getPostUrl } from "../../lib/url"; -async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - await allowCors(req, res); +type PostWithUrl = IPost & { url: string; plain_text_content: string }; + +type V1Response = PostWithUrl[] | null; +type V2Response = + | { posts: PostWithUrl[]; totalCount: number } + | { error: string } + | null; + +async function handleV1(req: NextApiRequest, res: NextApiResponse) { const hostname = String(req?.headers?.host); const { limit } = req?.query; @@ -30,22 +35,78 @@ async function handler( const pageUrl = getPageUrl(page, settings); const { posts } = await fetchPosts(String(page?.id), { - limit: Number(limit), + limit: limit ? Number(limit) : undefined, }); - const postsWithUrl = await Promise.all( - (posts ?? []).map((post) => ({ - ...post, - url: getPostUrl(pageUrl, post), - plain_text_content: convertMarkdownToPlainText(post.content), - })) - ); + const postsWithUrl = (posts ?? []).map((post) => ({ + ...post, + url: getPostUrl(pageUrl, post), + plain_text_content: convertMarkdownToPlainText(post.content), + })); res.status(200).json(postsWithUrl); } catch (e: unknown) { - console.log("Failed to fetch posts [Error]", e); + console.log("Failed to fetch posts [V1 Error]", e); res.status(200).json([]); } } +async function handleV2(req: NextApiRequest, res: NextApiResponse) { + const hostname = String(req?.headers?.host); + const { limit, offset } = req?.query; + + const { domain, page: url_slug } = translateHostToPageIdentifier(hostname); + + try { + const { page, settings } = await fetchRenderData( + String(domain || url_slug) + ); + + if (!page) { + res.status(404).json({ error: "Page not found" }); + return; + } + + if (!settings) { + res.status(404).json({ error: "Page settings not found" }); + return; + } + + const pageUrl = getPageUrl(page, settings); + const { posts, postsCount } = await fetchPostsWithPagination( + String(page?.id), + { + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + } + ); + + const postsWithUrl = (posts ?? []).map((post) => ({ + ...post, + url: getPostUrl(pageUrl, post), + plain_text_content: convertMarkdownToPlainText(post.content), + })); + + res.status(200).json({ posts: postsWithUrl, totalCount: postsCount }); + } catch (e: unknown) { + console.log("Failed to fetch posts [V2 Error]", e); + res.status(500).json({ error: "Failed to fetch posts" }); + } +} + +async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + await allowCors(req, res); + + const apiVersion = req.headers["x-api-version"]; + + if (apiVersion === "2") { + return handleV2(req, res); + } + + return handleV1(req, res); +} + export default handler; diff --git a/apps/page/pages/api/latest.ts b/apps/page/pages/api/latest.ts index df5d3fe..ab2fa84 100644 --- a/apps/page/pages/api/latest.ts +++ b/apps/page/pages/api/latest.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IPost } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IPost } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { allowCors } from "../../lib/cors"; import { fetchRenderData, translateHostToPageIdentifier } from "../../lib/data"; diff --git a/apps/page/pages/api/markdown.ts b/apps/page/pages/api/markdown.ts index d1e65ef..9e68c13 100644 --- a/apps/page/pages/api/markdown.ts +++ b/apps/page/pages/api/markdown.ts @@ -1,4 +1,4 @@ -import { DateTime } from "@changes-page/utils"; +import { DateTime } from "@changespage/utils"; import type { NextApiRequest, NextApiResponse } from "next"; import { fetchPosts, diff --git a/apps/page/pages/api/pa/view.ts b/apps/page/pages/api/pa/view.ts index 815ec08..6b2103d 100644 --- a/apps/page/pages/api/pa/view.ts +++ b/apps/page/pages/api/pa/view.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import UAParser from "ua-parser-js"; import { v4 } from "uuid"; diff --git a/apps/page/pages/api/pinned.ts b/apps/page/pages/api/pinned.ts index 7a4af86..4735e7c 100644 --- a/apps/page/pages/api/pinned.ts +++ b/apps/page/pages/api/pinned.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IPost } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IPost } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { allowCors } from "../../lib/cors"; import { fetchRenderData, translateHostToPageIdentifier } from "../../lib/data"; diff --git a/apps/page/pages/api/post/[id].ts b/apps/page/pages/api/post/[id].ts index d5cb884..546add5 100644 --- a/apps/page/pages/api/post/[id].ts +++ b/apps/page/pages/api/post/[id].ts @@ -1,6 +1,6 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IPost } from "@changes-page/supabase/types/page"; -import { convertMarkdownToPlainText } from "@changes-page/utils"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IPost } from "@changespage/supabase/types/page"; +import { convertMarkdownToPlainText } from "@changespage/utils"; import type { NextApiRequest, NextApiResponse } from "next"; import { allowCors } from "../../../lib/cors"; import { diff --git a/apps/page/pages/api/post/react.ts b/apps/page/pages/api/post/react.ts index 06d8a1c..47a2514 100644 --- a/apps/page/pages/api/post/react.ts +++ b/apps/page/pages/api/post/react.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { getVisitorId } from "../../../lib/visitor-auth"; diff --git a/apps/page/pages/api/post/reactions.ts b/apps/page/pages/api/post/reactions.ts index 4b3471b..e152270 100644 --- a/apps/page/pages/api/post/reactions.ts +++ b/apps/page/pages/api/post/reactions.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; diff --git a/apps/page/pages/api/posts.ts b/apps/page/pages/api/posts.ts index 8471460..86bad7e 100644 --- a/apps/page/pages/api/posts.ts +++ b/apps/page/pages/api/posts.ts @@ -1,6 +1,6 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; -import { IPost } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IErrorResponse } from "@changespage/supabase/types/api"; +import { IPost } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { PAGINATION_LIMIT } from "../../lib/data"; diff --git a/apps/page/pages/api/roadmap/submit-triage.ts b/apps/page/pages/api/roadmap/submit-triage.ts index d1e6939..d2a1c95 100644 --- a/apps/page/pages/api/roadmap/submit-triage.ts +++ b/apps/page/pages/api/roadmap/submit-triage.ts @@ -1,5 +1,5 @@ import arcjet, { detectBot, tokenBucket } from "@arcjet/next"; -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { escape } from "validator"; diff --git a/apps/page/pages/api/roadmap/vote.ts b/apps/page/pages/api/roadmap/vote.ts index 09c70d0..6e6363d 100644 --- a/apps/page/pages/api/roadmap/vote.ts +++ b/apps/page/pages/api/roadmap/vote.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { getVisitorId } from "../../../lib/visitor-auth"; diff --git a/apps/page/pages/api/roadmap/votes.ts b/apps/page/pages/api/roadmap/votes.ts index 4a61172..253028e 100644 --- a/apps/page/pages/api/roadmap/votes.ts +++ b/apps/page/pages/api/roadmap/votes.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; diff --git a/apps/page/pages/api/rss.ts b/apps/page/pages/api/rss.ts index dd5abf7..041a35e 100644 --- a/apps/page/pages/api/rss.ts +++ b/apps/page/pages/api/rss.ts @@ -1,7 +1,7 @@ import { convertMarkdownToHtml, convertMarkdownToPlainText, -} from "@changes-page/utils"; +} from "@changespage/utils"; import { Feed } from "feed"; import type { NextApiRequest, NextApiResponse } from "next"; import { diff --git a/apps/page/tailwind.config.js b/apps/page/tailwind.config.js index a8d3da2..f8af99a 100644 --- a/apps/page/tailwind.config.js +++ b/apps/page/tailwind.config.js @@ -5,7 +5,7 @@ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", - "./node_modules/@changes-page/ui/components/**/*.{js,ts,jsx,tsx}", + "./node_modules/@changespage/ui/components/**/*.{js,ts,jsx,tsx}", ], safelist: [ { diff --git a/apps/web/components/dialogs/ai-expand-concept-prompt-dialog.component.tsx b/apps/web/components/dialogs/ai-expand-concept-prompt-dialog.component.tsx index 9cf2437..20caf6b 100644 --- a/apps/web/components/dialogs/ai-expand-concept-prompt-dialog.component.tsx +++ b/apps/web/components/dialogs/ai-expand-concept-prompt-dialog.component.tsx @@ -1,5 +1,5 @@ import { useCompletion } from "@ai-sdk/react"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { Dialog, Transition } from "@headlessui/react"; import { LightningBoltIcon } from "@heroicons/react/solid"; import { Fragment, useEffect, useRef } from "react"; diff --git a/apps/web/components/dialogs/ai-prood-read-dialog.component.tsx b/apps/web/components/dialogs/ai-prood-read-dialog.component.tsx index dbc35a2..050c881 100644 --- a/apps/web/components/dialogs/ai-prood-read-dialog.component.tsx +++ b/apps/web/components/dialogs/ai-prood-read-dialog.component.tsx @@ -1,5 +1,5 @@ import { useCompletion } from "@ai-sdk/react"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { Dialog, Transition } from "@headlessui/react"; import { LightningBoltIcon } from "@heroicons/react/solid"; import { Fragment, useEffect, useRef } from "react"; diff --git a/apps/web/components/dialogs/ai-suggest-title-prompt-dialog.component.tsx b/apps/web/components/dialogs/ai-suggest-title-prompt-dialog.component.tsx index 36944f8..b2d2fea 100644 --- a/apps/web/components/dialogs/ai-suggest-title-prompt-dialog.component.tsx +++ b/apps/web/components/dialogs/ai-suggest-title-prompt-dialog.component.tsx @@ -1,4 +1,4 @@ -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { Dialog, Transition } from "@headlessui/react"; import { LightningBoltIcon } from "@heroicons/react/solid"; import { Fragment, useEffect, useRef, useState } from "react"; diff --git a/apps/web/components/dialogs/confirm-delete-dialog.component.tsx b/apps/web/components/dialogs/confirm-delete-dialog.component.tsx index c94075f..cd1fc13 100644 --- a/apps/web/components/dialogs/confirm-delete-dialog.component.tsx +++ b/apps/web/components/dialogs/confirm-delete-dialog.component.tsx @@ -1,5 +1,5 @@ /* This example requires Tailwind CSS v2.0+ */ -import { Spinner } from "@changes-page/ui"; +import { Spinner } from "@changespage/ui"; import { Dialog, Transition } from "@headlessui/react"; import { ExclamationIcon } from "@heroicons/react/outline"; import classNames from "classnames"; diff --git a/apps/web/components/dialogs/date-time-prompt-dialog.component.tsx b/apps/web/components/dialogs/date-time-prompt-dialog.component.tsx index 2ff20d7..289be0d 100644 --- a/apps/web/components/dialogs/date-time-prompt-dialog.component.tsx +++ b/apps/web/components/dialogs/date-time-prompt-dialog.component.tsx @@ -1,4 +1,4 @@ -import { DateTime } from "@changes-page/utils"; +import { DateTime } from "@changespage/utils"; import { Dialog, Transition } from "@headlessui/react"; import { ExclamationIcon } from "@heroicons/react/outline"; import { ClockIcon } from "@heroicons/react/solid"; diff --git a/apps/web/components/dialogs/manage-team-dialog.component.tsx b/apps/web/components/dialogs/manage-team-dialog.component.tsx index b406c09..9251df1 100644 --- a/apps/web/components/dialogs/manage-team-dialog.component.tsx +++ b/apps/web/components/dialogs/manage-team-dialog.component.tsx @@ -1,4 +1,4 @@ -import { ITeam } from "@changes-page/supabase/types/page"; +import { ITeam } from "@changespage/supabase/types/page"; import { Dialog, Transition } from "@headlessui/react"; import { PlusIcon } from "@heroicons/react/outline"; import { useFormik } from "formik"; diff --git a/apps/web/components/dialogs/warning-dialog.component.tsx b/apps/web/components/dialogs/warning-dialog.component.tsx index 806ac95..21e0502 100644 --- a/apps/web/components/dialogs/warning-dialog.component.tsx +++ b/apps/web/components/dialogs/warning-dialog.component.tsx @@ -1,5 +1,5 @@ /* This example requires Tailwind CSS v2.0+ */ -import { Spinner } from "@changes-page/ui"; +import { Spinner } from "@changespage/ui"; import { Dialog, Transition } from "@headlessui/react"; import { ExclamationIcon } from "@heroicons/react/outline"; import classNames from "classnames"; diff --git a/apps/web/components/entity/empty-state.tsx b/apps/web/components/entity/empty-state.tsx index 74ca515..27879ad 100644 --- a/apps/web/components/entity/empty-state.tsx +++ b/apps/web/components/entity/empty-state.tsx @@ -1,4 +1,4 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; +import { IPageSettings } from "@changespage/supabase/types/page"; import { ChevronRightIcon, PhotographIcon, diff --git a/apps/web/components/forms/page-form.component.tsx b/apps/web/components/forms/page-form.component.tsx index b5f962a..5da640e 100644 --- a/apps/web/components/forms/page-form.component.tsx +++ b/apps/web/components/forms/page-form.component.tsx @@ -2,8 +2,8 @@ import { IPage, PageType, PageTypeToLabel -} from "@changes-page/supabase/types/page"; -import { Spinner, SpinnerWithSpacing } from "@changes-page/ui"; +} from "@changespage/supabase/types/page"; +import { Spinner, SpinnerWithSpacing } from "@changespage/ui"; import classNames from "classnames"; import { FormikProps, useFormik } from "formik"; import { useRouter } from "next/router"; diff --git a/apps/web/components/forms/post-form.component.tsx b/apps/web/components/forms/post-form.component.tsx index d46b2a4..89786f4 100644 --- a/apps/web/components/forms/post-form.component.tsx +++ b/apps/web/components/forms/post-form.component.tsx @@ -4,9 +4,9 @@ import { PostStatus, PostType, PostTypeToLabel, -} from "@changes-page/supabase/types/page"; -import { PostTypeBadge, Spinner, SpinnerWithSpacing } from "@changes-page/ui"; -import { DateTime } from "@changes-page/utils"; +} from "@changespage/supabase/types/page"; +import { PostTypeBadge, Spinner, SpinnerWithSpacing } from "@changespage/ui"; +import { DateTime } from "@changespage/utils"; import { Listbox, Menu, Transition } from "@headlessui/react"; import { CalendarIcon, diff --git a/apps/web/components/layout/blog-layout.component.tsx b/apps/web/components/layout/blog-layout.component.tsx index dc95904..3b90a1e 100644 --- a/apps/web/components/layout/blog-layout.component.tsx +++ b/apps/web/components/layout/blog-layout.component.tsx @@ -1,4 +1,4 @@ -import { DateTime } from "@changes-page/utils"; +import { DateTime } from "@changespage/utils"; import { ArrowLeftIcon } from "@heroicons/react/solid"; import classNames from "classnames"; import Head from "next/head"; diff --git a/apps/web/components/marketing/changelog.tsx b/apps/web/components/marketing/changelog.tsx index 2501482..2e2c9e8 100644 --- a/apps/web/components/marketing/changelog.tsx +++ b/apps/web/components/marketing/changelog.tsx @@ -1,4 +1,4 @@ -import { IPost } from "@changes-page/supabase/types/page"; +import { IPost } from "@changespage/supabase/types/page"; import { Transition } from "@headlessui/react"; import { SpeakerphoneIcon } from "@heroicons/react/outline"; import { useRouter } from "next/router"; diff --git a/apps/web/components/marketing/hero.tsx b/apps/web/components/marketing/hero.tsx index b08d7a4..dce029a 100644 --- a/apps/web/components/marketing/hero.tsx +++ b/apps/web/components/marketing/hero.tsx @@ -74,9 +74,8 @@ export default function Hero({ stars = null }: { stars?: string | null }) { />

diff --git a/apps/web/components/page-settings/custom-domain.tsx b/apps/web/components/page-settings/custom-domain.tsx index c7b307d..3f39b36 100644 --- a/apps/web/components/page-settings/custom-domain.tsx +++ b/apps/web/components/page-settings/custom-domain.tsx @@ -1,5 +1,5 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import { useFormik } from "formik"; import { useState } from "react"; import * as Yup from "yup"; diff --git a/apps/web/components/page-settings/github-agent.tsx b/apps/web/components/page-settings/github-agent.tsx index c216f4d..fdd492f 100644 --- a/apps/web/components/page-settings/github-agent.tsx +++ b/apps/web/components/page-settings/github-agent.tsx @@ -1,5 +1,5 @@ -import { IGitHubInstallations } from "@changes-page/supabase/types/github"; -import { Spinner } from "@changes-page/ui"; +import { IGitHubInstallations } from "@changespage/supabase/types/github"; +import { Spinner } from "@changespage/ui"; import { ExternalLinkIcon, TrashIcon } from "@heroicons/react/outline"; import { useEffect, useState } from "react"; import { notifyError, notifySuccess } from "../core/toast.component"; diff --git a/apps/web/components/page-settings/integrations.tsx b/apps/web/components/page-settings/integrations.tsx index 1aabe8f..557abcc 100644 --- a/apps/web/components/page-settings/integrations.tsx +++ b/apps/web/components/page-settings/integrations.tsx @@ -1,5 +1,5 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import { useState } from "react"; import { v4 } from "uuid"; import { notifyInfo } from "../core/toast.component"; diff --git a/apps/web/components/page-settings/notifications.tsx b/apps/web/components/page-settings/notifications.tsx index a5c4f1f..e94e09a 100644 --- a/apps/web/components/page-settings/notifications.tsx +++ b/apps/web/components/page-settings/notifications.tsx @@ -1,5 +1,5 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import { InformationCircleIcon } from "@heroicons/react/solid"; import { useFormik } from "formik"; import { useCallback, useEffect, useState } from "react"; diff --git a/apps/web/components/page-settings/social-links.tsx b/apps/web/components/page-settings/social-links.tsx index b2f6a89..3abdc9e 100644 --- a/apps/web/components/page-settings/social-links.tsx +++ b/apps/web/components/page-settings/social-links.tsx @@ -1,8 +1,8 @@ import { useFormik } from "formik"; import { useState } from "react"; import * as Yup from "yup"; -import { IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import { InlineErrorMessage } from "../forms/notification.component"; const SOCIAL_KEY_TO_LABEL = { diff --git a/apps/web/components/page-settings/style.tsx b/apps/web/components/page-settings/style.tsx index f77e111..05ada67 100644 --- a/apps/web/components/page-settings/style.tsx +++ b/apps/web/components/page-settings/style.tsx @@ -1,6 +1,6 @@ -import { Database } from "@changes-page/supabase/types"; -import { IPage, IPageSettings } from "@changes-page/supabase/types/page"; -import { Spinner } from "@changes-page/ui"; +import { Database } from "@changespage/supabase/types"; +import { IPage, IPageSettings } from "@changespage/supabase/types/page"; +import { Spinner } from "@changespage/ui"; import fileExtension from "file-extension"; import dynamic from "next/dynamic"; import Image from "next/image"; diff --git a/apps/web/components/post/post-options.tsx b/apps/web/components/post/post-options.tsx index 2bac0d0..5b8b785 100644 --- a/apps/web/components/post/post-options.tsx +++ b/apps/web/components/post/post-options.tsx @@ -1,4 +1,4 @@ -import { PostStatus } from "@changes-page/supabase/types/page"; +import { PostStatus } from "@changespage/supabase/types/page"; import { Menu, Transition } from "@headlessui/react"; import { LocationMarkerIcon as RemovePinIcon } from "@heroicons/react/outline"; import { diff --git a/apps/web/components/post/post-status.tsx b/apps/web/components/post/post-status.tsx index 9f1ea04..83799b7 100644 --- a/apps/web/components/post/post-status.tsx +++ b/apps/web/components/post/post-status.tsx @@ -1,4 +1,4 @@ -import { PostStatus } from "@changes-page/supabase/types/page"; +import { PostStatus } from "@changespage/supabase/types/page"; import { CheckIcon, ClockIcon, PencilIcon } from "@heroicons/react/solid"; export const PostStatusToIcon = { diff --git a/apps/web/components/post/post.tsx b/apps/web/components/post/post.tsx index 188609e..95a5efb 100644 --- a/apps/web/components/post/post.tsx +++ b/apps/web/components/post/post.tsx @@ -6,9 +6,9 @@ import { PostStatus, PostStatusToLabel, PostType, -} from "@changes-page/supabase/types/page"; -import { PostDateTime, PostTypeBadge } from "@changes-page/ui"; -import { DateTime } from "@changes-page/utils"; +} from "@changespage/supabase/types/page"; +import { PostDateTime, PostTypeBadge } from "@changespage/ui"; +import { DateTime } from "@changespage/utils"; import { Menu } from "@headlessui/react"; import { DotsVerticalIcon } from "@heroicons/react/solid"; import classNames from "classnames"; diff --git a/apps/web/components/roadmap/RoadmapBoard.tsx b/apps/web/components/roadmap/RoadmapBoard.tsx index e5c746f..3fbab5b 100644 --- a/apps/web/components/roadmap/RoadmapBoard.tsx +++ b/apps/web/components/roadmap/RoadmapBoard.tsx @@ -3,7 +3,7 @@ import { IRoadmapCategory, IRoadmapColumn, IRoadmapTriageItem, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; import { useMemo, useState } from "react"; import RoadmapColumn from "./RoadmapColumn"; import RoadmapItemModal from "./RoadmapItemModal"; diff --git a/apps/web/components/roadmap/RoadmapColumn.tsx b/apps/web/components/roadmap/RoadmapColumn.tsx index e00cd93..3b95196 100644 --- a/apps/web/components/roadmap/RoadmapColumn.tsx +++ b/apps/web/components/roadmap/RoadmapColumn.tsx @@ -1,4 +1,4 @@ -import { IRoadmapColumn } from "@changes-page/supabase/types/page"; +import { IRoadmapColumn } from "@changespage/supabase/types/page"; import { PlusIcon } from "@heroicons/react/solid"; import RoadmapItem from "./RoadmapItem"; import { DragOverPosition, RoadmapItemWithRelations } from "./types"; diff --git a/apps/web/components/roadmap/RoadmapItem.tsx b/apps/web/components/roadmap/RoadmapItem.tsx index 380b235..2a86f54 100644 --- a/apps/web/components/roadmap/RoadmapItem.tsx +++ b/apps/web/components/roadmap/RoadmapItem.tsx @@ -1,4 +1,4 @@ -import { getCategoryColorClasses } from "@changes-page/utils"; +import { getCategoryColorClasses } from "@changespage/utils"; import { PencilIcon, TrashIcon } from "@heroicons/react/solid"; import classNames from "classnames"; import type React from "react"; diff --git a/apps/web/components/roadmap/RoadmapItemModal.tsx b/apps/web/components/roadmap/RoadmapItemModal.tsx index 192c777..f325d30 100644 --- a/apps/web/components/roadmap/RoadmapItemModal.tsx +++ b/apps/web/components/roadmap/RoadmapItemModal.tsx @@ -1,7 +1,7 @@ import { IRoadmapBoard, IRoadmapCategory, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; import { Dialog, Transition } from "@headlessui/react"; import { XIcon } from "@heroicons/react/outline"; import { Fragment, useCallback } from "react"; diff --git a/apps/web/components/roadmap/TriageItemCard.tsx b/apps/web/components/roadmap/TriageItemCard.tsx index 2c9eaf6..051e823 100644 --- a/apps/web/components/roadmap/TriageItemCard.tsx +++ b/apps/web/components/roadmap/TriageItemCard.tsx @@ -1,4 +1,4 @@ -import { IRoadmapTriageItem } from "@changes-page/supabase/types/page"; +import { IRoadmapTriageItem } from "@changespage/supabase/types/page"; import { TrashIcon } from "@heroicons/react/outline"; import { useState } from "react"; diff --git a/apps/web/components/roadmap/TriageRow.tsx b/apps/web/components/roadmap/TriageRow.tsx index 6682238..32dfa7b 100644 --- a/apps/web/components/roadmap/TriageRow.tsx +++ b/apps/web/components/roadmap/TriageRow.tsx @@ -1,4 +1,4 @@ -import { IRoadmapTriageItem } from "@changes-page/supabase/types/page"; +import { IRoadmapTriageItem } from "@changespage/supabase/types/page"; import { useState } from "react"; import { httpPost } from "../../utils/http"; import TriageItemCard from "./TriageItemCard"; diff --git a/apps/web/components/roadmap/hooks/useRoadmapDragDrop.ts b/apps/web/components/roadmap/hooks/useRoadmapDragDrop.ts index 6054a2e..5e07bfe 100644 --- a/apps/web/components/roadmap/hooks/useRoadmapDragDrop.ts +++ b/apps/web/components/roadmap/hooks/useRoadmapDragDrop.ts @@ -6,7 +6,7 @@ import { ItemsByColumn, RoadmapItemWithRelations, } from "../types"; -import { IRoadmapBoard } from "@changes-page/supabase/types/page"; +import { IRoadmapBoard } from "@changespage/supabase/types/page"; export function useRoadmapDragDrop({ itemsByColumn, diff --git a/apps/web/components/roadmap/hooks/useRoadmapItems.ts b/apps/web/components/roadmap/hooks/useRoadmapItems.ts index 0ff57ce..cce35cf 100644 --- a/apps/web/components/roadmap/hooks/useRoadmapItems.ts +++ b/apps/web/components/roadmap/hooks/useRoadmapItems.ts @@ -1,7 +1,7 @@ import { IRoadmapBoard, IRoadmapCategory, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; import { useState } from "react"; import { createAuditLog } from "../../../utils/auditLog"; import { useUserData } from "../../../utils/useUser"; diff --git a/apps/web/components/roadmap/types.ts b/apps/web/components/roadmap/types.ts index 9f94dcb..f8973b0 100644 --- a/apps/web/components/roadmap/types.ts +++ b/apps/web/components/roadmap/types.ts @@ -2,7 +2,7 @@ import { IRoadmapCategory, IRoadmapItem, IRoadmapVote, -} from "@changes-page/supabase/types/page"; +} from "@changespage/supabase/types/page"; export interface RoadmapItemWithRelations extends IRoadmapItem { roadmap_categories?: Pick; diff --git a/apps/web/data/schema.ts b/apps/web/data/schema.ts index ff97989..fb947d7 100644 --- a/apps/web/data/schema.ts +++ b/apps/web/data/schema.ts @@ -1,4 +1,4 @@ -import { PostStatus, PostType, URL_SLUG_REGEX } from "@changes-page/supabase/types/page"; +import { PostStatus, PostType, URL_SLUG_REGEX } from "@changespage/supabase/types/page"; import { array, boolean, mixed, object, string } from "yup"; export const NewPageSchema = object().shape({ diff --git a/apps/web/data/user.interface.ts b/apps/web/data/user.interface.ts index d05a95d..1a69b3c 100644 --- a/apps/web/data/user.interface.ts +++ b/apps/web/data/user.interface.ts @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { Stripe } from "stripe"; export type IUser = Database["public"]["Tables"]["users"]["Row"] & { diff --git a/apps/web/inngest/billing/report-pages-usage-invoice.ts b/apps/web/inngest/billing/report-pages-usage-invoice.ts index 4738568..fecf694 100644 --- a/apps/web/inngest/billing/report-pages-usage-invoice.ts +++ b/apps/web/inngest/billing/report-pages-usage-invoice.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import Stripe from "stripe"; import { v4 } from "uuid"; import inngestClient from "../../utils/inngest"; diff --git a/apps/web/inngest/email/send-roadmap-triage-notification.ts b/apps/web/inngest/email/send-roadmap-triage-notification.ts index 31f7a78..46e82ee 100644 --- a/apps/web/inngest/email/send-roadmap-triage-notification.ts +++ b/apps/web/inngest/email/send-roadmap-triage-notification.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { getAppBaseURL } from "../../utils/helpers"; import inngestClient from "../../utils/inngest"; import postmarkClient from "../../utils/postmark"; diff --git a/apps/web/inngest/email/send-visitor-magic-link.ts b/apps/web/inngest/email/send-visitor-magic-link.ts index c45365b..44e8e77 100644 --- a/apps/web/inngest/email/send-visitor-magic-link.ts +++ b/apps/web/inngest/email/send-visitor-magic-link.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import inngestClient from "../../utils/inngest"; import postmarkClient from "../../utils/postmark"; diff --git a/apps/web/inngest/jobs/delete-images.ts b/apps/web/inngest/jobs/delete-images.ts index 2704121..7dacde1 100644 --- a/apps/web/inngest/jobs/delete-images.ts +++ b/apps/web/inngest/jobs/delete-images.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import inngestClient from "../../utils/inngest"; export const DELETE_IMAGES_JOB_EVENT = "job/storage.delete_in_path"; diff --git a/apps/web/inngest/jobs/process-github-changelog.ts b/apps/web/inngest/jobs/process-github-changelog.ts index b6c178c..7fc3cbf 100644 --- a/apps/web/inngest/jobs/process-github-changelog.ts +++ b/apps/web/inngest/jobs/process-github-changelog.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { PostStatus } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { PostStatus } from "@changespage/supabase/types/page"; import { v4 } from "uuid"; import { generateChangelog } from "../../utils/changelog-ai"; import { diff --git a/apps/web/package.json b/apps/web/package.json index e8d397f..e070bf1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/web", + "name": "@changespage/web", "version": "1.20.0", "private": true, "scripts": { @@ -12,9 +12,10 @@ "dependencies": { "@ai-sdk/react": "^2.0.115", "@arcjet/next": "1.0.0-alpha.20", - "@changes-page/supabase": "workspace:*", - "@changes-page/ui": "workspace:*", - "@changes-page/utils": "workspace:*", + "@changespage/react": "workspace:*", + "@changespage/supabase": "workspace:*", + "@changespage/ui": "workspace:*", + "@changespage/utils": "workspace:*", "@headlessui/react": "^1.7.8", "@heroicons/react": "^1.0.3", "@json2csv/plainjs": "^6.1.3", diff --git a/apps/web/pages/account/billing.tsx b/apps/web/pages/account/billing.tsx index a3d5d98..a16a5e0 100644 --- a/apps/web/pages/account/billing.tsx +++ b/apps/web/pages/account/billing.tsx @@ -1,5 +1,5 @@ -import { SpinnerWithSpacing } from "@changes-page/ui"; -import { DateTime } from "@changes-page/utils"; +import { SpinnerWithSpacing } from "@changespage/ui"; +import { DateTime } from "@changespage/utils"; import { CurrencyDollarIcon } from "@heroicons/react/outline"; import { CalendarIcon } from "@heroicons/react/solid"; import classNames from "classnames"; diff --git a/apps/web/pages/api/billing/jobs/cleanup-inactive-pages.ts b/apps/web/pages/api/billing/jobs/cleanup-inactive-pages.ts index d2683d7..82403fc 100644 --- a/apps/web/pages/api/billing/jobs/cleanup-inactive-pages.ts +++ b/apps/web/pages/api/billing/jobs/cleanup-inactive-pages.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IErrorResponse } from "@changespage/supabase/types/api"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; diff --git a/apps/web/pages/api/billing/jobs/report-usage.ts b/apps/web/pages/api/billing/jobs/report-usage.ts index 3a8fa99..bed7bc0 100644 --- a/apps/web/pages/api/billing/jobs/report-usage.ts +++ b/apps/web/pages/api/billing/jobs/report-usage.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IErrorResponse } from "@changespage/supabase/types/api"; import type { NextApiRequest, NextApiResponse } from "next"; import { Stripe } from "stripe"; import { v4 } from "uuid"; diff --git a/apps/web/pages/api/emails/subscribers/export-csv.ts b/apps/web/pages/api/emails/subscribers/export-csv.ts index b265b7f..cd707d9 100644 --- a/apps/web/pages/api/emails/subscribers/export-csv.ts +++ b/apps/web/pages/api/emails/subscribers/export-csv.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { Parser } from "@json2csv/plainjs"; import { apiRateLimiter } from "../../../../utils/rate-limit"; import { withAuth } from "../../../../utils/withAuth"; diff --git a/apps/web/pages/api/emails/subscribers/index.ts b/apps/web/pages/api/emails/subscribers/index.ts index 499775b..6f79d5e 100644 --- a/apps/web/pages/api/emails/subscribers/index.ts +++ b/apps/web/pages/api/emails/subscribers/index.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { withAuth } from "../../../../utils/withAuth"; const getEmailSubscribers = withAuth<{ count: number }>( diff --git a/apps/web/pages/api/integrations/github/action-new-post.tsx b/apps/web/pages/api/integrations/github/action-new-post.tsx index c1ddef7..1cca641 100644 --- a/apps/web/pages/api/integrations/github/action-new-post.tsx +++ b/apps/web/pages/api/integrations/github/action-new-post.tsx @@ -1,5 +1,5 @@ -import { IErrorResponse } from "@changes-page/supabase/types/api"; -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { IErrorResponse } from "@changespage/supabase/types/api"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { diff --git a/apps/web/pages/api/integrations/github/installations.ts b/apps/web/pages/api/integrations/github/installations.ts index a575d26..e14bbcb 100644 --- a/apps/web/pages/api/integrations/github/installations.ts +++ b/apps/web/pages/api/integrations/github/installations.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IGitHubInstallations } from "@changes-page/supabase/types/github"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IGitHubInstallations } from "@changespage/supabase/types/github"; import { withAuth } from "../../../../utils/withAuth"; type ResponseData = diff --git a/apps/web/pages/api/integrations/github/webhook.ts b/apps/web/pages/api/integrations/github/webhook.ts index d6765ed..34b29d2 100644 --- a/apps/web/pages/api/integrations/github/webhook.ts +++ b/apps/web/pages/api/integrations/github/webhook.ts @@ -19,7 +19,7 @@ async function getRawBody(req: NextApiRequest): Promise { return Buffer.concat(chunks).toString("utf8"); } -const BOT_MENTIONS = ["@changepage", "@changes-page"]; +const BOT_MENTIONS = ["@changepage", "@changespage"]; export default async function handler( req: NextApiRequest, diff --git a/apps/web/pages/api/integrations/zapier/action-new-post.tsx b/apps/web/pages/api/integrations/zapier/action-new-post.tsx index 2bf3a9f..61b46dc 100644 --- a/apps/web/pages/api/integrations/zapier/action-new-post.tsx +++ b/apps/web/pages/api/integrations/zapier/action-new-post.tsx @@ -1,5 +1,5 @@ -import { IErrorResponse } from "@changes-page/supabase/types/api"; -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { IErrorResponse } from "@changespage/supabase/types/api"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { diff --git a/apps/web/pages/api/integrations/zapier/trigger-new-post.tsx b/apps/web/pages/api/integrations/zapier/trigger-new-post.tsx index bd73a47..41c264f 100644 --- a/apps/web/pages/api/integrations/zapier/trigger-new-post.tsx +++ b/apps/web/pages/api/integrations/zapier/trigger-new-post.tsx @@ -1,6 +1,6 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IErrorResponse } from "@changespage/supabase/types/api"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { getPageUrl, getPostUrl } from "../../../../utils/hooks/usePageUrl"; import { diff --git a/apps/web/pages/api/pages/new.ts b/apps/web/pages/api/pages/new.ts index 7e47cc7..b692b0c 100644 --- a/apps/web/pages/api/pages/new.ts +++ b/apps/web/pages/api/pages/new.ts @@ -1,4 +1,4 @@ -import { IPage } from "@changes-page/supabase/types/page"; +import { IPage } from "@changespage/supabase/types/page"; import { NewPageSchema } from "../../../data/schema"; import { apiRateLimiter } from "../../../utils/rate-limit"; import { diff --git a/apps/web/pages/api/pages/reactions.ts b/apps/web/pages/api/pages/reactions.ts index 9646f8c..68af241 100644 --- a/apps/web/pages/api/pages/reactions.ts +++ b/apps/web/pages/api/pages/reactions.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { withAuth } from "../../../utils/withAuth"; export default withAuth<{ ok: boolean; aggregate: any }>( diff --git a/apps/web/pages/api/pages/settings/index.ts b/apps/web/pages/api/pages/settings/index.ts index 27e30b2..1e24f5b 100644 --- a/apps/web/pages/api/pages/settings/index.ts +++ b/apps/web/pages/api/pages/settings/index.ts @@ -1,4 +1,4 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; +import { IPageSettings } from "@changespage/supabase/types/page"; import { createOrRetrievePageSettings } from "../../../../utils/useDatabase"; import { withAuth } from "../../../../utils/withAuth"; diff --git a/apps/web/pages/api/pages/settings/remove-domain.ts b/apps/web/pages/api/pages/settings/remove-domain.ts index 5e05738..4fcf5dc 100644 --- a/apps/web/pages/api/pages/settings/remove-domain.ts +++ b/apps/web/pages/api/pages/settings/remove-domain.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import isFQDN from "validator/lib/isFQDN"; import { withAuth } from "../../../../utils/withAuth"; diff --git a/apps/web/pages/api/pages/settings/webhook.ts b/apps/web/pages/api/pages/settings/webhook.ts index c69a57b..a973531 100644 --- a/apps/web/pages/api/pages/settings/webhook.ts +++ b/apps/web/pages/api/pages/settings/webhook.ts @@ -1,4 +1,4 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; +import { IPageSettings } from "@changespage/supabase/types/page"; import { NextApiRequest, NextApiResponse } from "next"; const databaseWebhook = async (req: NextApiRequest, res: NextApiResponse) => { diff --git a/apps/web/pages/api/pages/validate-url.ts b/apps/web/pages/api/pages/validate-url.ts index ac91cb8..4e3444c 100644 --- a/apps/web/pages/api/pages/validate-url.ts +++ b/apps/web/pages/api/pages/validate-url.ts @@ -1,5 +1,5 @@ -import { IErrorResponse } from "@changes-page/supabase/types/api"; -import { URL_SLUG_REGEX } from "@changes-page/supabase/types/page"; +import { IErrorResponse } from "@changespage/supabase/types/api"; +import { URL_SLUG_REGEX } from "@changespage/supabase/types/page"; import type { NextApiRequest, NextApiResponse } from "next"; import { apiRateLimiter } from "../../../utils/rate-limit"; import { validatePageByUrl } from "../../../utils/useDatabase"; diff --git a/apps/web/pages/api/pages/webhook.ts b/apps/web/pages/api/pages/webhook.ts index 8f249c0..dbb8b13 100644 --- a/apps/web/pages/api/pages/webhook.ts +++ b/apps/web/pages/api/pages/webhook.ts @@ -1,4 +1,4 @@ -import { IPage } from "@changes-page/supabase/types/page"; +import { IPage } from "@changespage/supabase/types/page"; import { NextApiRequest, NextApiResponse } from "next"; import { v4 } from "uuid"; import { DELETE_IMAGES_JOB_EVENT } from "../../../inngest/jobs/delete-images"; diff --git a/apps/web/pages/api/posts/index.ts b/apps/web/pages/api/posts/index.ts index 5061045..2c78c96 100644 --- a/apps/web/pages/api/posts/index.ts +++ b/apps/web/pages/api/posts/index.ts @@ -1,4 +1,4 @@ -import { PostStatus } from "@changes-page/supabase/types/page"; +import { PostStatus } from "@changespage/supabase/types/page"; import { NewPostSchema } from "../../../data/schema"; import { apiRateLimiter } from "../../../utils/rate-limit"; import { createPost } from "../../../utils/useDatabase"; diff --git a/apps/web/pages/api/posts/webhook.ts b/apps/web/pages/api/posts/webhook.ts index 67a02c1..3396f04 100644 --- a/apps/web/pages/api/posts/webhook.ts +++ b/apps/web/pages/api/posts/webhook.ts @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import { NextApiRequest, NextApiResponse } from "next"; import { DELETE_IMAGES_JOB_EVENT } from "../../../inngest/jobs/delete-images"; import { sendPostEmailToSubscribers } from "../../../utils/email"; diff --git a/apps/web/pages/api/roadmap/triage/delete.ts b/apps/web/pages/api/roadmap/triage/delete.ts index 1c9e07e..5edfc7e 100644 --- a/apps/web/pages/api/roadmap/triage/delete.ts +++ b/apps/web/pages/api/roadmap/triage/delete.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { createAuditLog } from "../../../../utils/auditLog"; import { withAuth } from "../../../../utils/withAuth"; diff --git a/apps/web/pages/api/roadmap/triage/move-to-board.ts b/apps/web/pages/api/roadmap/triage/move-to-board.ts index d6b5b0e..fafeb1e 100644 --- a/apps/web/pages/api/roadmap/triage/move-to-board.ts +++ b/apps/web/pages/api/roadmap/triage/move-to-board.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { v4 } from "uuid"; import { createAuditLog } from "../../../../utils/auditLog"; import { withAuth } from "../../../../utils/withAuth"; diff --git a/apps/web/pages/api/storage/get-mime-type.ts b/apps/web/pages/api/storage/get-mime-type.ts index 7f32fb2..91b0570 100644 --- a/apps/web/pages/api/storage/get-mime-type.ts +++ b/apps/web/pages/api/storage/get-mime-type.ts @@ -1,6 +1,6 @@ import mime from "mime-types"; import type { NextApiRequest, NextApiResponse } from "next"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; +import { IErrorResponse } from "@changespage/supabase/types/api"; const getMimeType = async ( req: NextApiRequest, diff --git a/apps/web/pages/api/teams/invite/accept/index.ts b/apps/web/pages/api/teams/invite/accept/index.ts index b8941cd..4c651b1 100644 --- a/apps/web/pages/api/teams/invite/accept/index.ts +++ b/apps/web/pages/api/teams/invite/accept/index.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { apiRateLimiter } from "../../../../../utils/rate-limit"; import { withAuth } from "../../../../../utils/withAuth"; diff --git a/apps/web/pages/api/teams/invite/index.ts b/apps/web/pages/api/teams/invite/index.ts index e66b923..eb1909e 100644 --- a/apps/web/pages/api/teams/invite/index.ts +++ b/apps/web/pages/api/teams/invite/index.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { ROUTES } from "../../../../data/routes.data"; import { getAppBaseURL } from "../../../../utils/helpers"; import inngestClient from "../../../../utils/inngest"; diff --git a/apps/web/pages/api/teams/member/[id]/index.ts b/apps/web/pages/api/teams/member/[id]/index.ts index 7d04744..471c04f 100644 --- a/apps/web/pages/api/teams/member/[id]/index.ts +++ b/apps/web/pages/api/teams/member/[id]/index.ts @@ -1,4 +1,4 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; +import { supabaseAdmin } from "@changespage/supabase/admin"; import { apiRateLimiter } from "../../../../../utils/rate-limit"; import { getUserById } from "../../../../../utils/useDatabase"; import { withAuth } from "../../../../../utils/withAuth"; diff --git a/apps/web/pages/blog/index.tsx b/apps/web/pages/blog/index.tsx index ea33054..246cf1d 100644 --- a/apps/web/pages/blog/index.tsx +++ b/apps/web/pages/blog/index.tsx @@ -1,4 +1,4 @@ -import { DateTime } from "@changes-page/utils"; +import { DateTime } from "@changespage/utils"; import { BookOpenIcon } from "@heroicons/react/solid"; import { SanityImageSource } from "@sanity/image-url/lib/types/types"; import classNames from "classnames"; diff --git a/apps/web/pages/changelog.tsx b/apps/web/pages/changelog.tsx new file mode 100644 index 0000000..c600e49 --- /dev/null +++ b/apps/web/pages/changelog.tsx @@ -0,0 +1,101 @@ +import { + createChangesPageClient, + ChangelogPost, + usePosts, +} from "@changespage/react"; +import type { GetStaticProps, InferGetStaticPropsType } from "next"; +import ReactMarkdown from "react-markdown"; +import FooterComponent from "../components/layout/footer.component"; +import HeaderComponent from "../components/layout/header.component"; +import { Spinner } from "@changespage/ui"; + +const POSTS_PER_PAGE = 10; + +const client = createChangesPageClient({ + baseUrl: process.env.VERCEL + ? "https://hey.changes.page" + : "http://localhost:3001", +}); + +export default function Changelog({ + initialPosts, + initialHasMore, +}: InferGetStaticPropsType) { + const { posts, hasMore, loading, loadMore } = usePosts({ + client, + initialData: { posts: initialPosts, hasMore: initialHasMore }, + limit: POSTS_PER_PAGE, + }); + + return ( +

+ + +
+

+ Changelog +

+

+ New updates and improvements to Changes.page +

+ +
+ {posts.map((post) => ( + + {({ title, content, tags, formattedDate }) => ( +
+ +

+ {title} +

+
+ {tags.map((tag) => ( + + {tag} + + ))} +
+
+ {/* @ts-ignore */} + {content} +
+
+ )} +
+ ))} +
+ + {hasMore && ( +
+ +
+ )} +
+ + +
+ ); +} + +export const getStaticProps: GetStaticProps = async () => { + const { posts, hasMore } = await client.getPosts({ limit: POSTS_PER_PAGE }); + + return { + props: { + initialPosts: posts, + initialHasMore: hasMore, + }, + revalidate: 86400, + }; +}; diff --git a/apps/web/pages/free-tools/ai-changelog-generator.tsx b/apps/web/pages/free-tools/ai-changelog-generator.tsx index ff5237a..06e4f6a 100644 --- a/apps/web/pages/free-tools/ai-changelog-generator.tsx +++ b/apps/web/pages/free-tools/ai-changelog-generator.tsx @@ -1,5 +1,5 @@ import { useCompletion } from "@ai-sdk/react"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { LightningBoltIcon, RefreshIcon } from "@heroicons/react/solid"; import classNames from "classnames"; import Link from "next/link"; diff --git a/apps/web/pages/onboarding/open-page.tsx b/apps/web/pages/onboarding/open-page.tsx index b43b3b8..6c28004 100644 --- a/apps/web/pages/onboarding/open-page.tsx +++ b/apps/web/pages/onboarding/open-page.tsx @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { ROUTES } from "../../data/routes.data"; import { withSupabase } from "../../utils/supabase/withSupabase"; diff --git a/apps/web/pages/pages/[page_id]/[post_id].tsx b/apps/web/pages/pages/[page_id]/[post_id].tsx index 589fff8..01f6619 100644 --- a/apps/web/pages/pages/[page_id]/[post_id].tsx +++ b/apps/web/pages/pages/[page_id]/[post_id].tsx @@ -1,4 +1,4 @@ -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import { InferGetServerSidePropsType } from "next"; import { useRouter } from "next/router"; import { useState, type JSX } from "react"; diff --git a/apps/web/pages/pages/[page_id]/analytics.tsx b/apps/web/pages/pages/[page_id]/analytics.tsx index 7021a3f..1d5e78d 100644 --- a/apps/web/pages/pages/[page_id]/analytics.tsx +++ b/apps/web/pages/pages/[page_id]/analytics.tsx @@ -1,5 +1,5 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { InferGetServerSidePropsType } from "next"; import Image from "next/image"; import AuthLayout from "../../../components/layout/auth-layout.component"; diff --git a/apps/web/pages/pages/[page_id]/audit-logs.tsx b/apps/web/pages/pages/[page_id]/audit-logs.tsx index 9c3291e..7bec678 100644 --- a/apps/web/pages/pages/[page_id]/audit-logs.tsx +++ b/apps/web/pages/pages/[page_id]/audit-logs.tsx @@ -1,6 +1,6 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { Timeline } from "@changes-page/ui"; -import { DateTime } from "@changes-page/utils"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { Timeline } from "@changespage/ui"; +import { DateTime } from "@changespage/utils"; import { ClockIcon, PencilIcon, diff --git a/apps/web/pages/pages/[page_id]/edit.tsx b/apps/web/pages/pages/[page_id]/edit.tsx index 1b22d6f..621b1b4 100644 --- a/apps/web/pages/pages/[page_id]/edit.tsx +++ b/apps/web/pages/pages/[page_id]/edit.tsx @@ -1,4 +1,4 @@ -import { IPage } from "@changes-page/supabase/types/page"; +import { IPage } from "@changespage/supabase/types/page"; import { InferGetServerSidePropsType } from "next"; import { useRouter } from "next/router"; import { useState } from "react"; diff --git a/apps/web/pages/pages/[page_id]/index.tsx b/apps/web/pages/pages/[page_id]/index.tsx index b89b0a8..cd39bcd 100644 --- a/apps/web/pages/pages/[page_id]/index.tsx +++ b/apps/web/pages/pages/[page_id]/index.tsx @@ -1,5 +1,5 @@ -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; -import { Timeline } from "@changes-page/ui"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; +import { Timeline } from "@changespage/ui"; import { ChartBarIcon, CodeIcon, diff --git a/apps/web/pages/pages/[page_id]/new.tsx b/apps/web/pages/pages/[page_id]/new.tsx index 2f9b6bf..a4cd119 100644 --- a/apps/web/pages/pages/[page_id]/new.tsx +++ b/apps/web/pages/pages/[page_id]/new.tsx @@ -1,4 +1,4 @@ -import { PostStatus } from "@changes-page/supabase/types/page"; +import { PostStatus } from "@changespage/supabase/types/page"; import { InferGetServerSidePropsType } from "next"; import { useRouter } from "next/router"; import { useState, type JSX } from "react"; diff --git a/apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx b/apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx index fb2f1b1..8ebae42 100644 --- a/apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx +++ b/apps/web/pages/pages/[page_id]/roadmap/[board_id]/settings.tsx @@ -2,7 +2,7 @@ import { getCategoryColorClasses, getCategoryColorOptions, ROADMAP_COLORS, -} from "@changes-page/utils"; +} from "@changespage/utils"; import { GlobeIcon, LockClosedIcon, MenuIcon } from "@heroicons/react/outline"; import { PencilIcon, PlusIcon, TrashIcon } from "@heroicons/react/solid"; import { InferGetServerSidePropsType } from "next"; diff --git a/apps/web/pages/pages/[page_id]/settings/[activeTab].tsx b/apps/web/pages/pages/[page_id]/settings/[activeTab].tsx index 83c7848..4061f00 100644 --- a/apps/web/pages/pages/[page_id]/settings/[activeTab].tsx +++ b/apps/web/pages/pages/[page_id]/settings/[activeTab].tsx @@ -1,4 +1,4 @@ -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { InferGetServerSidePropsType } from "next"; import dynamic from "next/dynamic"; import { useMemo } from "react"; diff --git a/apps/web/pages/pages/index.tsx b/apps/web/pages/pages/index.tsx index eb3ce8a..2b24a9a 100644 --- a/apps/web/pages/pages/index.tsx +++ b/apps/web/pages/pages/index.tsx @@ -1,4 +1,4 @@ -import { PageType, PageTypeToLabel } from "@changes-page/supabase/types/page"; +import { PageType, PageTypeToLabel } from "@changespage/supabase/types/page"; import { PlusIcon, UserGroupIcon } from "@heroicons/react/solid"; import classNames from "classnames"; import type { InferGetServerSidePropsType } from "next"; diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx index d6363d9..58e633a 100644 --- a/apps/web/pages/teams/index.tsx +++ b/apps/web/pages/teams/index.tsx @@ -1,5 +1,5 @@ -import { IPage } from "@changes-page/supabase/types/page"; -import { SpinnerWithSpacing } from "@changes-page/ui"; +import { IPage } from "@changespage/supabase/types/page"; +import { SpinnerWithSpacing } from "@changespage/ui"; import { CheckCircleIcon, OfficeBuildingIcon, diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js index f0d95c2..10d0aa6 100644 --- a/apps/web/tailwind.config.js +++ b/apps/web/tailwind.config.js @@ -4,7 +4,7 @@ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", - "./node_modules/@changes-page/ui/components/**/*.{js,ts,jsx,tsx}", + "./node_modules/@changespage/ui/components/**/*.{js,ts,jsx,tsx}", "./node_modules/streamdown/dist/*.js", ], safelist: [ diff --git a/apps/web/utils/changelog-ai.ts b/apps/web/utils/changelog-ai.ts index f6423ca..8108cd1 100644 --- a/apps/web/utils/changelog-ai.ts +++ b/apps/web/utils/changelog-ai.ts @@ -2,7 +2,7 @@ import { generateObject } from "ai"; import { z } from "zod"; import { openRouter } from "./ai-gateway"; import { getPRCommits, getPRDetails, getPRFiles } from "./github"; -import { PostType } from "@changes-page/supabase/types/page"; +import { PostType } from "@changespage/supabase/types/page"; export interface ChangelogInput { pr: Awaited>; diff --git a/apps/web/utils/email.ts b/apps/web/utils/email.ts index 4266491..8291283 100644 --- a/apps/web/utils/email.ts +++ b/apps/web/utils/email.ts @@ -1,9 +1,9 @@ -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IPage, IPageSettings, IPost } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IPage, IPageSettings, IPost } from "@changespage/supabase/types/page"; import { convertMarkdownToHtml, convertMarkdownToPlainText, -} from "@changes-page/utils"; +} from "@changespage/utils"; import { getPageUrl, getPostUrl } from "./hooks/usePageUrl"; import inngestClient from "./inngest"; import { getUserById } from "./useDatabase"; diff --git a/apps/web/utils/hooks/usePage.ts b/apps/web/utils/hooks/usePage.ts index 8dc5915..b970493 100644 --- a/apps/web/utils/hooks/usePage.ts +++ b/apps/web/utils/hooks/usePage.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import { notifyError } from "../../components/core/toast.component"; -import { IPage } from "@changes-page/supabase/types/page"; +import { IPage } from "@changespage/supabase/types/page"; import { useUserData } from "../useUser"; export default function usePage(pageId: string, prefetch = true) { diff --git a/apps/web/utils/hooks/usePageSettings.ts b/apps/web/utils/hooks/usePageSettings.ts index 55870eb..767d8e4 100644 --- a/apps/web/utils/hooks/usePageSettings.ts +++ b/apps/web/utils/hooks/usePageSettings.ts @@ -1,4 +1,4 @@ -import { IPageSettings } from "@changes-page/supabase/types/page"; +import { IPageSettings } from "@changespage/supabase/types/page"; import { useEffect, useState } from "react"; import { notifyError, diff --git a/apps/web/utils/hooks/usePageUrl.ts b/apps/web/utils/hooks/usePageUrl.ts index 557a7e9..886c2a1 100644 --- a/apps/web/utils/hooks/usePageUrl.ts +++ b/apps/web/utils/hooks/usePageUrl.ts @@ -1,4 +1,4 @@ -import { IPage, IPageSettings, IPost } from "@changes-page/supabase/types/page"; +import { IPage, IPageSettings, IPost } from "@changespage/supabase/types/page"; import { useCallback, useMemo } from "react"; import slugify from "slugify"; diff --git a/apps/web/utils/hooks/usePosts.ts b/apps/web/utils/hooks/usePosts.ts index 145923a..a429827 100644 --- a/apps/web/utils/hooks/usePosts.ts +++ b/apps/web/utils/hooks/usePosts.ts @@ -1,4 +1,4 @@ -import { IPost, PostStatus } from "@changes-page/supabase/types/page"; +import { IPost, PostStatus } from "@changespage/supabase/types/page"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { useDebounce } from "use-debounce"; diff --git a/apps/web/utils/supabase/client.ts b/apps/web/utils/supabase/client.ts index 9bfd5ba..e42ffee 100644 --- a/apps/web/utils/supabase/client.ts +++ b/apps/web/utils/supabase/client.ts @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { createBrowserClient } from "@supabase/ssr"; export function createClient() { diff --git a/apps/web/utils/supabase/middleware.ts b/apps/web/utils/supabase/middleware.ts index 51d3cfe..a38dbec 100644 --- a/apps/web/utils/supabase/middleware.ts +++ b/apps/web/utils/supabase/middleware.ts @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { createServerClient } from "@supabase/ssr"; import { NextRequest, NextResponse } from "next/server"; diff --git a/apps/web/utils/supabase/server.ts b/apps/web/utils/supabase/server.ts index 1049c04..4c121f6 100644 --- a/apps/web/utils/supabase/server.ts +++ b/apps/web/utils/supabase/server.ts @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { createServerClient, serializeCookieHeader } from "@supabase/ssr"; import { GetServerSidePropsContext, diff --git a/apps/web/utils/supabase/withSupabase.ts b/apps/web/utils/supabase/withSupabase.ts index 1b206ba..7124c00 100644 --- a/apps/web/utils/supabase/withSupabase.ts +++ b/apps/web/utils/supabase/withSupabase.ts @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { SupabaseClient, User } from "@supabase/supabase-js"; import { GetServerSidePropsContext, GetServerSidePropsResult } from "next"; import { createServerClientSSR } from "./server"; diff --git a/apps/web/utils/useDatabase.ts b/apps/web/utils/useDatabase.ts index 130141f..aea8c34 100644 --- a/apps/web/utils/useDatabase.ts +++ b/apps/web/utils/useDatabase.ts @@ -1,9 +1,9 @@ -import { Json } from "@changes-page/supabase/types"; +import { Json } from "@changespage/supabase/types"; import { Stripe } from "stripe"; -import { supabaseAdmin } from "@changes-page/supabase/admin"; -import { IAnalyticsData } from "@changes-page/supabase/types/api"; -import { IPage } from "@changes-page/supabase/types/page"; +import { supabaseAdmin } from "@changespage/supabase/admin"; +import { IAnalyticsData } from "@changespage/supabase/types/api"; +import { IPage } from "@changespage/supabase/types/page"; import { IUser } from "../data/user.interface"; import { VALID_STRIPE_PRICE_IDS } from "../pages/api/billing/jobs/report-usage"; diff --git a/apps/web/utils/useSSR.ts b/apps/web/utils/useSSR.ts index fd15fa4..dee907b 100644 --- a/apps/web/utils/useSSR.ts +++ b/apps/web/utils/useSSR.ts @@ -1,5 +1,5 @@ import { SupabaseClient } from "@supabase/supabase-js"; -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; export async function getPage(supabase: SupabaseClient, id: string) { const { data: page } = await supabase diff --git a/apps/web/utils/useUser.tsx b/apps/web/utils/useUser.tsx index 9c83455..2ff6d75 100644 --- a/apps/web/utils/useUser.tsx +++ b/apps/web/utils/useUser.tsx @@ -1,4 +1,4 @@ -import { Database } from "@changes-page/supabase/types"; +import { Database } from "@changespage/supabase/types"; import { AuthError, Session, diff --git a/apps/web/utils/withAuth.ts b/apps/web/utils/withAuth.ts index f3d86d1..05ea9ef 100644 --- a/apps/web/utils/withAuth.ts +++ b/apps/web/utils/withAuth.ts @@ -1,5 +1,5 @@ -import { Database } from "@changes-page/supabase/types"; -import { IErrorResponse } from "@changes-page/supabase/types/api"; +import { Database } from "@changespage/supabase/types"; +import { IErrorResponse } from "@changespage/supabase/types/api"; import { SupabaseClient, User } from "@supabase/supabase-js"; import type { NextApiRequest, NextApiResponse } from "next"; import { createServerClientForAPI } from "./supabase/server"; diff --git a/packages/react-sdk/README.md b/packages/react-sdk/README.md new file mode 100644 index 0000000..4df7450 --- /dev/null +++ b/packages/react-sdk/README.md @@ -0,0 +1,86 @@ +# @changespage/react + +Embed your changelog in any React app. + +## Installation + +```bash +npm install @changespage/react +``` + +## Usage + +```tsx +import { createChangesPageClient, ChangelogPost } from '@changespage/react'; +import ReactMarkdown from 'react-markdown'; + +const client = createChangesPageClient({ + baseUrl: 'https://yourpage.changes.page' +}); + +export default async function ChangelogPage() { + const { posts, hasMore } = await client.getPosts({ limit: 10 }); + + return ( +
+ {posts.map(post => ( + + {({ title, content, tags, formattedDate, url }) => ( +
+

{title}

+ +
{tags.map(t => {t})}
+ {content} +
+ )} +
+ ))} +
+ ); +} +``` + +## API + +### `createChangesPageClient(config)` + +| Option | Type | Description | +|--------|------|-------------| +| `baseUrl` | `string` | Your changes-page URL | + +### `client.getPosts(options?)` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `limit` | `number` | 10 | Posts per page (max 50) | +| `offset` | `number` | 0 | Pagination offset | + +Returns `{ posts, totalCount, hasMore }` + +### `client.getLatestPost()` + +Returns the most recent post or `null`. + +### `` + +Render prop component exposing: + +- `id`, `title`, `content` (markdown), `plainText`, `tags`, `date`, `formattedDate`, `url` + +## Hook + +### `usePosts` + +```tsx +import { usePosts } from '@changespage/react'; + +// Client-only (fetches on mount) +const { posts, hasMore, loading, loadMore } = usePosts({ client, limit: 10 }); + +// With SSR initial data (skips initial fetch) +const { posts, hasMore, loading, loadMore } = usePosts({ + client, + initialData: { posts: initialPosts, hasMore: initialHasMore }, + limit: 10, +}); +``` diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json new file mode 100644 index 0000000..ee1cd19 --- /dev/null +++ b/packages/react-sdk/package.json @@ -0,0 +1,39 @@ +{ + "name": "@changespage/react", + "version": "0.1.0", + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rimraf dist && tsc", + "prepublishOnly": "npm run build" + }, + "dependencies": {}, + "devDependencies": { + "@types/react": "^18.3.18", + "react": "^18.3.1", + "rimraf": "^6.1.0", + "typescript": "^5.3.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + }, + "author": "Arjun Komath ", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/techulus/changes-page.git" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/react-sdk/src/client.ts b/packages/react-sdk/src/client.ts new file mode 100644 index 0000000..3a1907b --- /dev/null +++ b/packages/react-sdk/src/client.ts @@ -0,0 +1,59 @@ +import type { + ChangesPageClient, + ClientConfig, + GetPostsOptions, + GetPostsResult, + Post, +} from "./types"; + +interface ApiResponse { + posts: Post[]; + totalCount: number; +} + +const API_VERSION = "2"; + +export function createChangesPageClient(config: ClientConfig): ChangesPageClient { + const baseUrl = config.baseUrl.replace(/\/$/, ""); + + async function getPosts(options?: GetPostsOptions): Promise { + const limit = options?.limit ?? 10; + const offset = options?.offset ?? 0; + + const params = new URLSearchParams({ + limit: String(limit), + offset: String(offset), + }); + + const response = await fetch(`${baseUrl}/api/json?${params.toString()}`, { + headers: { + "X-API-Version": API_VERSION, + }, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + const errorMessage = + (errorData as { error?: string }).error || response.statusText; + throw new Error(errorMessage); + } + + const data: ApiResponse = await response.json(); + + return { + posts: data.posts, + totalCount: data.totalCount, + hasMore: offset + data.posts.length < data.totalCount, + }; + } + + async function getLatestPost(): Promise { + const result = await getPosts({ limit: 1 }); + return result.posts[0] ?? null; + } + + return { + getPosts, + getLatestPost, + }; +} diff --git a/packages/react-sdk/src/components/ChangelogPost.tsx b/packages/react-sdk/src/components/ChangelogPost.tsx new file mode 100644 index 0000000..06c8765 --- /dev/null +++ b/packages/react-sdk/src/components/ChangelogPost.tsx @@ -0,0 +1,17 @@ +import type { ChangelogPostProps, ChangelogPostRenderProps } from "../types"; +import { formatDate, parseDate } from "../utils"; + +export function ChangelogPost({ post, children }: ChangelogPostProps) { + const renderProps: ChangelogPostRenderProps = { + id: post.id, + title: post.title, + content: post.content, + plainText: post.plain_text_content, + tags: post.tags, + date: parseDate(post.publication_date), + formattedDate: formatDate(post.publication_date), + url: post.url, + }; + + return <>{children(renderProps)}; +} diff --git a/packages/react-sdk/src/components/index.ts b/packages/react-sdk/src/components/index.ts new file mode 100644 index 0000000..6f45873 --- /dev/null +++ b/packages/react-sdk/src/components/index.ts @@ -0,0 +1 @@ +export { ChangelogPost } from "./ChangelogPost"; diff --git a/packages/react-sdk/src/hooks/index.ts b/packages/react-sdk/src/hooks/index.ts new file mode 100644 index 0000000..35e8fb8 --- /dev/null +++ b/packages/react-sdk/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { usePosts } from "./usePosts"; +export type { UsePostsOptions, UsePostsResult } from "./usePosts"; diff --git a/packages/react-sdk/src/hooks/usePosts.ts b/packages/react-sdk/src/hooks/usePosts.ts new file mode 100644 index 0000000..aa50f35 --- /dev/null +++ b/packages/react-sdk/src/hooks/usePosts.ts @@ -0,0 +1,94 @@ +"use client"; + +import { useState, useCallback, useEffect, useRef } from "react"; +import type { ChangesPageClient, Post } from "../types"; + +export interface UsePostsInitialData { + posts: Post[]; + hasMore: boolean; +} + +export interface UsePostsOptions { + client: ChangesPageClient; + initialData?: UsePostsInitialData; + limit?: number; +} + +export interface UsePostsResult { + posts: Post[]; + hasMore: boolean; + loading: boolean; + error: Error | null; + loadMore: () => Promise; +} + +export function usePosts({ + client, + initialData, + limit = 10, +}: UsePostsOptions): UsePostsResult { + const [posts, setPosts] = useState(initialData?.posts ?? []); + const [hasMore, setHasMore] = useState(initialData?.hasMore ?? true); + const [loading, setLoading] = useState(!initialData); + const [error, setError] = useState(null); + const didFetch = useRef(!!initialData); + + useEffect(() => { + if (didFetch.current) return; + didFetch.current = true; + + let cancelled = false; + + async function fetchInitial() { + try { + const result = await client.getPosts({ limit, offset: 0 }); + if (!cancelled) { + setPosts(result.posts); + setHasMore(result.hasMore); + } + } catch (e) { + if (!cancelled) { + setError(e instanceof Error ? e : new Error(String(e))); + } + } finally { + if (!cancelled) { + setLoading(false); + } + } + } + + fetchInitial(); + + return () => { + cancelled = true; + }; + }, [client, limit]); + + const loadMore = useCallback(async () => { + if (loading || !hasMore) return; + + setLoading(true); + setError(null); + + try { + const result = await client.getPosts({ + limit, + offset: posts.length, + }); + setPosts((prev) => [...prev, ...result.posts]); + setHasMore(result.hasMore); + } catch (e) { + setError(e instanceof Error ? e : new Error(String(e))); + } finally { + setLoading(false); + } + }, [client, posts.length, limit, loading, hasMore]); + + return { + posts, + hasMore, + loading, + error, + loadMore, + }; +} diff --git a/packages/react-sdk/src/index.ts b/packages/react-sdk/src/index.ts new file mode 100644 index 0000000..65e5e86 --- /dev/null +++ b/packages/react-sdk/src/index.ts @@ -0,0 +1,15 @@ +export { createChangesPageClient } from "./client"; +export { ChangelogPost } from "./components"; +export { usePosts } from "./hooks"; +export { formatDate, getTagLabel, parseDate } from "./utils"; +export type { + ChangesPageClient, + ChangelogPostProps, + ChangelogPostRenderProps, + ClientConfig, + GetPostsOptions, + GetPostsResult, + Post, + PostTag, +} from "./types"; +export type { UsePostsOptions, UsePostsResult } from "./hooks"; diff --git a/packages/react-sdk/src/types.ts b/packages/react-sdk/src/types.ts new file mode 100644 index 0000000..f582541 --- /dev/null +++ b/packages/react-sdk/src/types.ts @@ -0,0 +1,51 @@ +import type { ReactNode } from "react"; + +export type PostTag = "fix" | "new" | "improvement" | "announcement" | "alert"; + +export interface Post { + id: string; + title: string; + content: string; + tags: PostTag[]; + publication_date: string | null; + updated_at: string; + created_at: string; + url: string; + plain_text_content: string; +} + +export interface ClientConfig { + baseUrl: string; +} + +export interface GetPostsOptions { + limit?: number; + offset?: number; +} + +export interface GetPostsResult { + posts: Post[]; + totalCount: number; + hasMore: boolean; +} + +export interface ChangelogPostRenderProps { + id: string; + title: string; + content: string; + plainText: string; + tags: PostTag[]; + date: Date | null; + formattedDate: string; + url: string; +} + +export interface ChangelogPostProps { + post: Post; + children: (props: ChangelogPostRenderProps) => ReactNode; +} + +export interface ChangesPageClient { + getPosts: (options?: GetPostsOptions) => Promise; + getLatestPost: () => Promise; +} diff --git a/packages/react-sdk/src/utils.ts b/packages/react-sdk/src/utils.ts new file mode 100644 index 0000000..0ff9ef8 --- /dev/null +++ b/packages/react-sdk/src/utils.ts @@ -0,0 +1,29 @@ +import type { PostTag } from "./types"; + +const tagLabels: Record = { + fix: "Fix", + new: "New", + improvement: "Improvement", + announcement: "Announcement", + alert: "Alert", +}; + +export function getTagLabel(tag: PostTag): string { + return tagLabels[tag] ?? tag; +} + +export function formatDate(dateString: string | null): string { + if (!dateString) return ""; + + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +export function parseDate(dateString: string | null): Date | null { + if (!dateString) return null; + return new Date(dateString); +} diff --git a/packages/react-sdk/tsconfig.json b/packages/react-sdk/tsconfig.json new file mode 100644 index 0000000..4950778 --- /dev/null +++ b/packages/react-sdk/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "jsx": "react-jsx", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "rootDir": "./src", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/supabase/package.json b/packages/supabase/package.json index ed288b7..977d166 100644 --- a/packages/supabase/package.json +++ b/packages/supabase/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/supabase", + "name": "@changespage/supabase", "version": "1.0.0", "exports": { "./types": "./dist/types/index.js", diff --git a/packages/ui/package.json b/packages/ui/package.json index bf1ca74..9265ef0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/ui", + "name": "@changespage/ui", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/utils/package.json b/packages/utils/package.json index 8233c89..890a3c2 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,5 +1,5 @@ { - "name": "@changes-page/utils", + "name": "@changespage/utils", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a4ffa9..98a4dff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,13 +59,13 @@ importers: '@arcjet/next': specifier: 1.0.0-alpha.20 version: 1.0.0-alpha.20(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(next@14.2.35(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@changes-page/supabase': + '@changespage/supabase': specifier: workspace:* version: link:../../packages/supabase - '@changes-page/ui': + '@changespage/ui': specifier: workspace:* version: link:../../packages/ui - '@changes-page/utils': + '@changespage/utils': specifier: workspace:* version: link:../../packages/utils '@headlessui/react': @@ -228,13 +228,16 @@ importers: '@arcjet/next': specifier: 1.0.0-alpha.20 version: 1.0.0-alpha.20(@bufbuild/protobuf@1.10.0)(@connectrpc/connect@1.4.0(@bufbuild/protobuf@1.10.0))(next@14.2.35(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@changes-page/supabase': + '@changespage/react': + specifier: workspace:* + version: link:../../packages/react-sdk + '@changespage/supabase': specifier: workspace:* version: link:../../packages/supabase - '@changes-page/ui': + '@changespage/ui': specifier: workspace:* version: link:../../packages/ui - '@changes-page/utils': + '@changespage/utils': specifier: workspace:* version: link:../../packages/utils '@headlessui/react': @@ -452,6 +455,21 @@ importers: specifier: ^5 version: 5.8.3 + packages/react-sdk: + devDependencies: + '@types/react': + specifier: ^18.3.18 + version: 18.3.21 + react: + specifier: ^18.3.1 + version: 18.3.1 + rimraf: + specifier: ^6.1.0 + version: 6.1.2 + typescript: + specifier: ^5.3.3 + version: 5.8.3 + packages/supabase: dependencies: '@supabase/supabase-js': @@ -1079,6 +1097,14 @@ packages: cpu: [x64] os: [win32] + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -3745,6 +3771,10 @@ packages: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true + glob@13.0.0: + resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} + engines: {node: 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -4734,6 +4764,10 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -5033,6 +5067,10 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} + engines: {node: 20 || >=22} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -5439,6 +5477,11 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true + rimraf@6.1.2: + resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==} + engines: {node: 20 || >=22} + hasBin: true + robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -6721,6 +6764,12 @@ snapshots: '@img/sharp-win32-x64@0.34.1': optional: true + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -7990,7 +8039,7 @@ snapshots: '@types/hoist-non-react-statics@3.3.6': dependencies: - '@types/react': 18.3.1 + '@types/react': 18.3.21 hoist-non-react-statics: 3.3.2 '@types/json5@0.0.29': {} @@ -9798,6 +9847,12 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + glob@13.0.0: + dependencies: + minimatch: 10.1.1 + minipass: 7.1.2 + path-scurry: 2.0.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -11323,6 +11378,10 @@ snapshots: minimalistic-assert@1.0.1: {} + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -11633,6 +11692,11 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-scurry@2.0.1: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + path-to-regexp@8.3.0: {} pathe@2.0.3: {} @@ -12148,6 +12212,11 @@ snapshots: dependencies: glob: 10.4.5 + rimraf@6.1.2: + dependencies: + glob: 13.0.0 + package-json-from-dist: 1.0.1 + robust-predicates@3.0.2: {} rollup@2.79.2: From ac49b49397dad1f2a115f967d221e35e26f74f86 Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 19 Dec 2025 21:49:07 +1100 Subject: [PATCH 2/4] Disable before merge --- apps/web/pages/changelog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/pages/changelog.tsx b/apps/web/pages/changelog.tsx index c600e49..effa9ae 100644 --- a/apps/web/pages/changelog.tsx +++ b/apps/web/pages/changelog.tsx @@ -89,12 +89,12 @@ export default function Changelog({ } export const getStaticProps: GetStaticProps = async () => { - const { posts, hasMore } = await client.getPosts({ limit: POSTS_PER_PAGE }); + // const { posts, hasMore } = await client.getPosts({ limit: POSTS_PER_PAGE }); return { props: { - initialPosts: posts, - initialHasMore: hasMore, + initialPosts: [], + initialHasMore: false, }, revalidate: 86400, }; From 42e2331e0646cbe65d15b4b4903dbc772fed89ed Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 19 Dec 2025 21:59:33 +1100 Subject: [PATCH 3/4] Address CR --- apps/web/inngest/email/send-visitor-magic-link.ts | 1 - apps/web/pages/changelog.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web/inngest/email/send-visitor-magic-link.ts b/apps/web/inngest/email/send-visitor-magic-link.ts index 44e8e77..9cf5f6c 100644 --- a/apps/web/inngest/email/send-visitor-magic-link.ts +++ b/apps/web/inngest/email/send-visitor-magic-link.ts @@ -17,7 +17,6 @@ export const sendVisitorMagicLink = inngestClient.createFunction( event.data; console.log("Sending magic link email", { - email, page_url, page_id, }); diff --git a/apps/web/pages/changelog.tsx b/apps/web/pages/changelog.tsx index effa9ae..caed649 100644 --- a/apps/web/pages/changelog.tsx +++ b/apps/web/pages/changelog.tsx @@ -12,7 +12,7 @@ import { Spinner } from "@changespage/ui"; const POSTS_PER_PAGE = 10; const client = createChangesPageClient({ - baseUrl: process.env.VERCEL + baseUrl: process.env.VERCEL_ENV ? "https://hey.changes.page" : "http://localhost:3001", }); From d78938604909125395fc9d3c98dc76859847b26b Mon Sep 17 00:00:00 2001 From: Arjun Komath Date: Fri, 19 Dec 2025 22:17:56 +1100 Subject: [PATCH 4/4] Fix mentions check for github bot --- apps/web/pages/api/integrations/github/webhook.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/pages/api/integrations/github/webhook.ts b/apps/web/pages/api/integrations/github/webhook.ts index 34b29d2..11380cc 100644 --- a/apps/web/pages/api/integrations/github/webhook.ts +++ b/apps/web/pages/api/integrations/github/webhook.ts @@ -19,7 +19,7 @@ async function getRawBody(req: NextApiRequest): Promise { return Buffer.concat(chunks).toString("utf8"); } -const BOT_MENTIONS = ["@changepage", "@changespage"]; +const BOT_MENTIONS = ["@changes-page", "@changespage"]; export default async function handler( req: NextApiRequest,