From 638e750b1a417810abf83f8935e7dd6db8068a6a Mon Sep 17 00:00:00 2001 From: sarah <129242944+sarah-inkeep@users.noreply.github.com> Date: Mon, 29 Jun 2026 16:07:41 -0700 Subject: [PATCH] feat(open-knowledge): add product-update signup to docs footer (#2264) * feat(open-knowledge): add product-update signup to docs footer Add a server-only POST /api/subscribe route that creates a Resend contact tied to a configured segment, and a footer signup form built with react-hook-form and shadcn Field components. The Resend API key and segment id are read server-side only (RESEND_API_KEY, RESEND_SEGMENT_ID) and never reach the client bundle. Also extend the left dot texture down into the footer for visual continuity with the call-to-action section. * fix(open-knowledge): conform shadcn Field components to repo lint Format and lint-fix the vendored shadcn ui components (field, input, label, separator) to the docs Biome config: organize imports, type-only imports, strict equality, stable list keys. Replace the banned useMemo in FieldError with a plain computation (React Compiler memoizes). Remove the unused button.tsx (the footer form uses MarketingButton). * fix(open-knowledge): regenerate third-party notices, address review nits Regenerate THIRD_PARTY_NOTICES.md for the new docs deps (resend, react-hook-form, @hookform/resolvers, class-variance-authority) so the notices drift check passes. Log error.message alongside error.name on Resend failures, log network errors in the form's catch block, and disable spellcheck on the email input. GitOrigin-RevId: 1b6ed868ac855e0e3f2ce1d6dacd177f411d1b87 --- THIRD_PARTY_NOTICES.md | 4 +- bun.lock | 18 +- docs/.env.example | 5 + docs/package.json | 4 + docs/src/app/(home)/footer.tsx | 10 +- docs/src/app/api/subscribe/route.ts | 48 +++++ docs/src/components/subscribe-form.tsx | 101 +++++++++++ docs/src/components/ui/field.tsx | 235 +++++++++++++++++++++++++ docs/src/components/ui/input.tsx | 21 +++ docs/src/components/ui/label.tsx | 21 +++ docs/src/components/ui/separator.tsx | 28 +++ 11 files changed, 489 insertions(+), 6 deletions(-) create mode 100644 docs/src/app/api/subscribe/route.ts create mode 100644 docs/src/components/subscribe-form.tsx create mode 100644 docs/src/components/ui/field.tsx create mode 100644 docs/src/components/ui/input.tsx create mode 100644 docs/src/components/ui/label.tsx create mode 100644 docs/src/components/ui/separator.tsx diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 7eea912a3..f97bcf2df 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1245,7 +1245,7 @@ Homepage: https://github.com/honojs/node-server Copyright (c) 2022 - present, Yusuke Wada and Hono contributors -### `@hookform/resolvers@5.2.2` +### `@hookform/resolvers@5.4.0` Homepage: https://react-hook-form.com Copyright (c) 2019-present Beier(Bill) Luo @@ -4880,7 +4880,7 @@ Homepage: https://github.com/vasturiano/react-force-graph _(No LICENSE file in package; SPDX identifier in `package.json` is the sole declared grant.)_ -### `react-hook-form@7.74.0` +### `react-hook-form@7.80.0` Homepage: https://react-hook-form.com Copyright (c) 2019-present Beier(Bill) Luo diff --git a/bun.lock b/bun.lock index 34baf09f0..4384ae53e 100644 --- a/bun.lock +++ b/bun.lock @@ -27,6 +27,7 @@ "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.41.0", "@floating-ui/dom": "^1.7.6", + "@hookform/resolvers": "^5.4.0", "@inkeep/cxkit-react": "^0.5.117", "@inkeep/open-knowledge-core": "workspace:*", "@radix-ui/react-popover": "^1.1.17", @@ -37,6 +38,7 @@ "@tiptap/react": "^3.22.3", "@tiptap/suggestion": "^3.22.3", "@types/mdx": "^2.0.13", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "fumadocs-core": "~16.1.0", "fumadocs-mdx": "~14.0.3", @@ -49,6 +51,8 @@ "radix-ui": "^1.4.3", "react": "^19", "react-dom": "^19", + "react-hook-form": "^7.80.0", + "resend": "^6.16.0", "tailwind-merge": "^3.6.0", "zod": "^4.3.6", }, @@ -742,7 +746,7 @@ "@hono/node-server": ["@hono/node-server@1.19.13", "", { "peerDependencies": { "hono": "^4" } }, "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ=="], - "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], + "@hookform/resolvers": ["@hookform/resolvers@5.4.0", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw=="], "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], @@ -1504,6 +1508,8 @@ "@size-limit/time": ["@size-limit/time@12.1.0", "", { "dependencies": { "estimo": "^3.0.5" }, "peerDependencies": { "size-limit": "12.1.0" } }, "sha512-ekYPeZcvkPSLsHtqNmz7F5jx3R0HV7CpY7kGasBW2yKR3NrD0JWMAcswS9OCR8OzK9hyLACRTNYTpLI9PXLczQ=="], + "@stablelib/base64": ["@stablelib/base64@1.0.1", "", {}, "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], @@ -2518,6 +2524,8 @@ "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], @@ -3430,6 +3438,8 @@ "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + "postal-mime": ["postal-mime@2.7.4", "", {}, "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g=="], + "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], @@ -3570,7 +3580,7 @@ "react-force-graph-2d": ["react-force-graph-2d@1.29.1", "", { "dependencies": { "force-graph": "^1.51", "prop-types": "15", "react-kapsule": "^2.5" }, "peerDependencies": { "react": "*" } }, "sha512-1Rl/1Z3xy2iTHKj6a0jRXGyiI86xUti81K+jBQZ+Oe46csaMikp47L5AjrzA9hY9fNGD63X8ffrqnvaORukCuQ=="], - "react-hook-form": ["react-hook-form@7.74.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-yR6wHr99p9wFv686jhRWVSFhUvDvNbdUf2dKlbno8/VKOCuoNobDGC6S+M2dua9A9Yo8vpcrp8assIYbsZCQ9g=="], + "react-hook-form": ["react-hook-form@7.80.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-4P+fk6oXsxY+6xSj7Euhc2sumQD8zQqCuVHoJwoyp9EchP+IUW9OESB7uHFJOKsIBQ4MQqYE84INJFqUCYNoOg=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], @@ -3666,6 +3676,8 @@ "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], + "resend": ["resend@6.16.0", "", { "dependencies": { "postal-mime": "2.7.4", "standardwebhooks": "1.0.0" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-SaKISwHtxvAxneF84Njgnzg+zdngUu1vOT/paRU1De9QF+zXQR3GnwJHSh+mpJPjUhsGD4WxYi5CfKkXMkDqwg=="], + "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], @@ -3832,6 +3844,8 @@ "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + "standardwebhooks": ["standardwebhooks@1.0.0", "", { "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" } }, "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg=="], + "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], diff --git a/docs/.env.example b/docs/.env.example index b02949aca..8953279c1 100644 --- a/docs/.env.example +++ b/docs/.env.example @@ -1,3 +1,8 @@ NEXT_PUBLIC_INKEEP_API_KEY="" NEXT_PUBLIC_POSTHOG_KEY="" NEXT_PUBLIC_POSTHOG_HOST="https://us.posthog.com" + +# Server-only. Powers POST /api/subscribe (product-update signups via Resend). +# Never prefix with NEXT_PUBLIC_ — these must not reach the client bundle. +RESEND_API_KEY="" +RESEND_SEGMENT_ID="" diff --git a/docs/package.json b/docs/package.json index bed0baac7..c85d84e49 100644 --- a/docs/package.json +++ b/docs/package.json @@ -22,6 +22,7 @@ "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.41.0", "@floating-ui/dom": "^1.7.6", + "@hookform/resolvers": "^5.4.0", "@inkeep/cxkit-react": "^0.5.117", "@inkeep/open-knowledge-core": "workspace:*", "@radix-ui/react-popover": "^1.1.17", @@ -32,6 +33,7 @@ "@tiptap/react": "^3.22.3", "@tiptap/suggestion": "^3.22.3", "@types/mdx": "^2.0.13", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "fumadocs-core": "~16.1.0", "fumadocs-mdx": "~14.0.3", @@ -44,6 +46,8 @@ "radix-ui": "^1.4.3", "react": "^19", "react-dom": "^19", + "react-hook-form": "^7.80.0", + "resend": "^6.16.0", "tailwind-merge": "^3.6.0", "zod": "^4.3.6" }, diff --git a/docs/src/app/(home)/footer.tsx b/docs/src/app/(home)/footer.tsx index d01f8182f..253feb1aa 100644 --- a/docs/src/app/(home)/footer.tsx +++ b/docs/src/app/(home)/footer.tsx @@ -3,6 +3,8 @@ import { DiscordIcon } from '@/components/icons/discord'; import { GitHubIcon } from '@/components/icons/github'; import { XIcon } from '@/components/icons/x'; import { InkeepLogo } from '@/components/inkeep-logo'; +import { SubscribeForm } from '@/components/subscribe-form'; +import { DotTexture } from './dot-texture'; const socialLinks = [ { href: 'https://github.com/inkeep/open-knowledge', label: 'GitHub', Icon: GitHubIcon }, @@ -17,8 +19,12 @@ const legalLinks = [ export function SiteFooter() { return ( -