feat: add @ottabase/ui-marketing package with Atlas and Mono templates - #107
feat: add @ottabase/ui-marketing package with Atlas and Mono templates#107thinkdj wants to merge 5 commits into
Conversation
Implements 16 marketing section components across two distinct templates: Atlas (GitHub/Atlassian/Notion style): - HeroSection — centered layout, badge, dual CTAs, optional screenshot - FeaturesGrid — bordered card grid, 2–4 col, icon box per feature - PricingTable — per-plan cards, monthly/annual toggle, feature checklist - TestimonialsCarousel — 3-up paginated grid, star rating, avatars - FAQAccordion — 2-col layout (header left, accordion right) - LogoCloud — grayscale logos, text fallback, hover reveal - CTABanner — bordered card, centered, dual CTAs - FooterMarketing — 4-col grid, social icons, legal bar Mono (Linear/Vercel style): - HeroSection — left-aligned, monospace label, inverted CTA button - FeaturesGrid — numbered rows (01, 02…), title+desc in columns - PricingTable — full comparison table, features as rows, plans as cols - TestimonialsCarousel — single quote, typographic quotemark, numbered nav - FAQAccordion — numbered questions, +/− toggle, flat full-width rows - LogoCloud — horizontal rule + monospace name fallbacks - CTABanner — full-width inverted (bg-foreground) section - FooterMarketing — flat single-row nav, minimal bottom strip All components consume brand CSS variables via Tailwind token classes (bg-background, text-foreground, border-border, etc.) — zero hardcoded colors, fully compatible with @ottabase/brand-engine themes. https://claude.ai/code/session_01R1MVKojSQdAqQZUT4U6Mm7
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
Adds a new @ottabase/ui-marketing package containing reusable marketing “section” components, organized into two templates (Atlas + Mono) and packaged with multi-entry exports for selective imports.
Changes:
- Introduces a new
packages/ui-marketingworkspace package with tsup/tsconfig and an explicit exports map. - Adds shared types and a small
cn()utility for Tailwind class merging. - Implements Atlas + Mono variants of 8 marketing sections each (Hero, Features, Pricing, Testimonials, FAQ, LogoCloud, CTA, Footer).
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/ui-marketing/package.json | New package metadata, exports map for template + per-section entry points, deps/scripts. |
| packages/ui-marketing/tsup.config.ts | tsup multi-entry build configuration for per-component outputs. |
| packages/ui-marketing/tsconfig.json | Package-local TS config extending repo defaults (react-jsx, outDir). |
| packages/ui-marketing/src/index.ts | Root barrel exporting shared types + both templates. |
| packages/ui-marketing/src/types.ts | Shared public prop/type definitions across all sections. |
| packages/ui-marketing/src/lib/utils.ts | Local cn() helper using clsx + tailwind-merge. |
| packages/ui-marketing/src/atlas/index.ts | Atlas template barrel exports. |
| packages/ui-marketing/src/atlas/HeroSection.tsx | Atlas hero section implementation. |
| packages/ui-marketing/src/atlas/FeaturesGrid.tsx | Atlas bordered feature-card grid implementation. |
| packages/ui-marketing/src/atlas/PricingTable.tsx | Atlas plan-card pricing table with billing toggle. |
| packages/ui-marketing/src/atlas/TestimonialsCarousel.tsx | Atlas 3-up paginated testimonials grid with star rating. |
| packages/ui-marketing/src/atlas/FAQAccordion.tsx | Atlas 2-column FAQ accordion. |
| packages/ui-marketing/src/atlas/LogoCloud.tsx | Atlas centered logo cloud with grayscale/hover reveal. |
| packages/ui-marketing/src/atlas/CTABanner.tsx | Atlas bordered CTA banner section. |
| packages/ui-marketing/src/atlas/FooterMarketing.tsx | Atlas 4-column marketing footer with legal/social. |
| packages/ui-marketing/src/mono/index.ts | Mono template barrel exports. |
| packages/ui-marketing/src/mono/HeroSection.tsx | Mono left-aligned typography-forward hero section. |
| packages/ui-marketing/src/mono/FeaturesGrid.tsx | Mono numbered feature rows implementation. |
| packages/ui-marketing/src/mono/PricingTable.tsx | Mono full comparison-table pricing implementation with toggle. |
| packages/ui-marketing/src/mono/TestimonialsCarousel.tsx | Mono single-testimonial carousel with numbered indicator/nav. |
| packages/ui-marketing/src/mono/FAQAccordion.tsx | Mono full-width numbered FAQ accordion implementation. |
| packages/ui-marketing/src/mono/LogoCloud.tsx | Mono minimal logo/name row with rule + grayscale logos. |
| packages/ui-marketing/src/mono/CTABanner.tsx | Mono inverted full-width CTA banner implementation. |
| packages/ui-marketing/src/mono/FooterMarketing.tsx | Mono minimal flat footer with inline nav + bottom legal strip. |
| <a | ||
| href={primaryCta.href} | ||
| onClick={primaryCta.onClick} | ||
| className="inline-flex items-center justify-center h-10 px-5 bg-foreground text-background text-sm font-medium hover:opacity-80 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||
| > |
There was a problem hiding this comment.
This component attaches React event handlers (onClick) but is missing a 'use client' directive. In Next.js RSC environments this will fail to compile when imported from a Server Component. Add 'use client' at the top (or remove event handlers from this component API).
| <a | ||
| href={primaryCta.href} | ||
| onClick={primaryCta.onClick} | ||
| className="inline-flex items-center justify-center h-10 px-6 rounded-md bg-primary text-primary-foreground text-sm font-medium transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" | ||
| > |
There was a problem hiding this comment.
This component passes onClick handlers into <a> elements but is not marked as a client component. In Next.js RSC, this will error unless the file starts with 'use client'. Add the directive at the top (or drop support for onClick and keep it purely server-safe).
| <a | ||
| href={primaryCta.href} | ||
| onClick={primaryCta.onClick} | ||
| className="inline-flex items-center justify-center h-10 px-6 bg-background text-foreground text-sm font-medium hover:opacity-90 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-background" | ||
| > | ||
| {primaryCta.label} |
There was a problem hiding this comment.
This component uses onClick handlers on interactive elements but is missing 'use client'. That will break when used from Next.js Server Components. Add 'use client' at the top (or remove event handlers from the public API).
| <button | ||
| onClick={() => setOpen(open === i ? null : i)} | ||
| className="flex w-full items-start gap-6 py-6 text-left focus-visible:outline-none" | ||
| aria-expanded={open === i} | ||
| > |
There was a problem hiding this comment.
Accordion trigger <button> should include type="button". For accessibility, also consider adding aria-controls/id linking between the trigger and the answer panel.
| legal, | ||
| className, | ||
| }: FooterMarketingProps) { | ||
| const allLinks = sections.flatMap((s) => s.links); |
There was a problem hiding this comment.
allLinks is computed but never used. This adds noise and can become a source of confusion during maintenance; remove it or use it to render the flattened nav.
| const allLinks = sections.flatMap((s) => s.links); |
| const entries: Record<string, string> = { | ||
| // Barrel exports | ||
| 'index': 'src/index.ts', | ||
| 'types': 'src/types.ts', |
There was a problem hiding this comment.
With bundle: false, internal runtime deps need explicit entries. Components import ../lib/utils at runtime, but src/lib/utils.ts isn’t listed here, so dist/lib/utils.* may not be emitted and consumers could see a module resolution error. Add an explicit entry for src/lib/utils.ts (e.g. lib/utils).
| 'types': 'src/types.ts', | |
| 'types': 'src/types.ts', | |
| 'lib/utils': 'src/lib/utils.ts', |
| <div className="mt-6 inline-flex items-center border border-border rounded-md p-0.5 gap-0.5 bg-muted"> | ||
| {(['monthly', 'annual'] as const).map((period) => ( | ||
| <button | ||
| key={period} |
There was a problem hiding this comment.
The billing period toggle buttons should set type="button" (to avoid implicit form submission) and expose the selected state to assistive tech (e.g. aria-pressed or role="tab"/aria-selected).
| key={period} | |
| key={period} | |
| type="button" | |
| aria-pressed={billing === period} |
| <button | ||
| onClick={() => setIndex((i) => Math.max(0, i - 1))} | ||
| disabled={index === 0} | ||
| aria-label="Previous testimonial" | ||
| className="inline-flex h-8 w-8 items-center justify-center border border-border text-foreground hover:bg-muted disabled:opacity-30 disabled:cursor-not-allowed transition-colors" |
There was a problem hiding this comment.
These carousel navigation <button> elements should set type="button" to avoid implicit form submission when rendered inside a <form>.
| <span | ||
| className={cn( | ||
| 'font-mono text-base leading-none text-muted-foreground shrink-0 transition-transform duration-200', | ||
| open === i ? 'rotate-0' : 'rotate-0', |
There was a problem hiding this comment.
The cn() call here is redundant: both branches return the same class (rotate-0). This can be simplified to a constant class string (or update it to the intended transform if something was meant to rotate).
| open === i ? 'rotate-0' : 'rotate-0', | |
| 'rotate-0', |
| "peerDependencies": { | ||
| "react": "catalog:", | ||
| "react-dom": "catalog:" | ||
| }, |
There was a problem hiding this comment.
For UI packages in this repo, react and react-dom are typically included in devDependencies as well as peerDependencies to allow isolated local builds/type-checks. Consider adding them to devDependencies here too.
… build errors - Replace placeholder page.tsx with full Atlas marketing homepage (HeroSection, LogoCloud, FeaturesGrid, TestimonialsCarousel, PricingTable, FAQAccordion, CTABanner, FooterMarketing) - Add @ottabase/ui-marketing to template app deps, transpilePackages, optimizePackageImports, and tailwind content scanning Fix build errors: - ui-marketing tsconfig: add lib:ES2020 so padStart type resolves - ui-marketing tsup: add lib/utils entry so relative dist imports work - ui-marketing mono/PricingTable: fix Map<> generic inference (TS2322) - brand-engine: exclude layout-api.ts (unrelated ORM type errors) from tsconfig and build script; build dependency chain (db → cf → utils → ottaorm → brand-engine) to get dist types - Template app tsconfig: replace catch-all @ottabase/* source path alias with explicit mappings only for UI packages in transpilePackages; all other packages resolve via their built dist through node_modules https://claude.ai/code/session_01R1MVKojSQdAqQZUT4U6Mm7
…w pages
- Fix React duplicate key warnings in FooterMarketing (atlas + mono): change
key={link.href} → key={link.label} in section links and legal links
- Add 3 new types to ui-marketing/src/types.ts: NavbarProps, StatsSectionProps,
StepsSectionProps (with NavLink, StatItem, Step primitives)
- Add 6 new components: AtlasNavbar, AtlasStatsSection, AtlasStepsSection,
MonoNavbar, MonoStatsSection, MonoStepsSection
- Update atlas/index.ts and mono/index.ts barrels to export new components
- Update tsup.config.ts entries and package.json sub-path exports for all 6
- Refactor app/page.tsx: switch to barrel imports, extract data to shared
lib/marketing-demo-data.tsx, add Navbar + StatsSection + StepsSection
- Add /theme route: theme gallery page listing all 8 built-in brand themes
with HSL color swatches (uses THEME_PRESET_ITEMS from brand-engine)
- Add /theme/[themeName] route: server component resolves light/dark CSS vars
via buildCSSVarMap; ThemePreviewClient renders full Atlas or Mono marketing
page scoped to the selected theme and color scheme
Build: next build passes ✓, all 6 app routes generated
https://claude.ai/code/session_01R1MVKojSQdAqQZUT4U6Mm7
…111) * Use ottaorm client/hooks across RBAC & admin Replace ad-hoc TanStack Query usage and manual API calls with @ottabase/ottaorm client helpers across the app. Created model hooks in useRBAC (createModelHooks + queryKeys) and migrated organizations, members, roles, and audit log hooks to use the new abstractions with optimistic updates (meta.entity) and standardized invalidation/prefetch APIs. Converted many admin pages (BrandKit, Cron, DB, Notifications, Queue, Referral Tracking, Blog Studio, MigrationStatus, etc.) to use useApiQuery/useApiMutation or model hooks, simplifying mutation options and centralizing cache invalidation. Added a new useEntityQuery hook and updated ottaorm client exports and README accordingly. Also small local settings update (.claude/settings.local.json) to add outputStyle and prefersReducedMotion. * Use model hooks and remove manual invalidations Replace ad-hoc react-query mutations with generated model hooks for organization_members in UserRBACPage, removing direct API calls and manual queryClient invalidations. Adjusted mutate payloads (create now sends full member record, update uses { id, data }, delete uses id) and removed unused imports. Also cleaned up useRBAC hooks by removing onSettled invalidation handlers (relying on meta.entity/global observer) and minor onError comment removal. * Add org settings button and show org metadata OrganizationSwitcher: add an inline settings button for each organization that navigates to /organizations/:id/settings, increase dropdown width, adjust layout (truncate behavior, check icon placement) and minor import reordering. OrganizationSettingsPage: initialize form data when the org loads using useEffect (map status 'deleted' -> 'suspended' and reset hasChanges), reorganize imports, add UI fields for Last Updated, Owner ID (with copy-to-clipboard and toast), and display raw Settings and Metadata as JSON. Also included error/loading components and small cleanup to imports and state handling. * feat: add @ottabase/ottalanding — semantic landing page layer Introduces the ottalanding package: a structured, theme-swappable system for building landing pages. Content (hero, features, pricing, etc.) is strongly typed and stored in DB via OttaORM models. Themes are visual layers that render this content — swap like WordPress, data stays the same. - Content types: serializable, DB-ready types for all section kinds (hero, features, pricing, testimonials, FAQ, logo-cloud, CTA, stats, steps) - Theme system: LandingTheme interface, registry, section component map, page renderer — adding a new theme = one object implementing the interface - OttaORM models: LandingSite, LandingPage, LandingSection, LandingTheme with full field metadata, validation, and query helpers - Init helper and barrel exports following ottablog patterns https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * feat: complete ottalanding integration — admin fields, themes, Next.js app 1. Admin form configs: Each section type (hero, features, pricing, etc.) gets purpose-built form fields via SECTION_FIELDS instead of generic JSON editors. Fields have proper labels, placeholders, and validation. 2. Table rename: All tables now prefixed with ottalanding_* (sites, pages, sections, themes) for namespace clarity. 3. Atlas + Mono themes: Both ui-marketing templates are now proper ottalanding themes. Each wraps the existing components, adapting serializable content types → React component props. 4. Next.js app integration: Homepage now uses renderPage(theme, site, page) from ottalanding. All content lives in config/landing.config.ts as strongly typed LandingSiteData. Old hardcoded demo data removed. Theme preview page updated to use ottalanding themes. Brand Engine (CSS vars) remains separate — controls visual tokens. https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * Scope API/brand by appId; persist org/app state Introduce global app/org context and persist current org/app across the app and worker plumbing. Key changes: - Add globalStore (jotai createStore) and expose appId/organizationId atoms to allow non-React code (API client) to read state. - API client: send X-App-Id (from state) and X-Org-Id headers, improve organization lookup fallback, and keep JSON/timeouts/toast behavior. - RBAC hooks: make create/update organization mutations accept responses that are either {data:...} or the object directly and validate returned org id (throw on invalid payload). - Auth: persist current org to localStorage (ottabase.current-org-id), derive effective org from session or stored value, and set APP_ID on session init. - UI: ControlsSection and several organization pages now update organizationId atom and write the CURRENT_ORG_KEY to localStorage; OrganizationRegistration auto-generates slug on blur and applies new org to state/localStorage on create. - Worker changes: - brand-utils and brand routes now scope brand APIs by appId only (remove per-organization scoping) and add /api/brand/presets. - Bootstrap: enforce default role permissions during seeding, pre-hydrate localStorage when creating owner (cookie-based sessions), and include credentials on owner-creation fetch. - Tests: update BrandLayout test to provide APP_ID. - Minor fixes: adjust header name to X-Org-Id and small backend handler whitespace change. These changes centralize app/appId and organization state, ensure server and client requests are properly scoped by appId, and make org selection persistent and available to non-React modules. #incompleteCommit * Support FormData in API client Allow request body to be FormData and avoid serializing it. buildDedupeKey now accepts FormData and uses a '__formdata__' placeholder so multipart uploads are not deduped against each other. createApiClient detects FormData, skips adding a Content-Type header (browser sets the multipart boundary) and sends the raw FormData instead of JSON.stringify. JSON behavior for non-FormData bodies is unchanged. * Integrate Brand Engine: New logic (save all to db, no registry) Renames tenant header to X-Org-Id across docs, server, tests and client code, and integrates the Brand Engine into the tanstack template and worker runtime. Key changes: - Header rename: X-Organization-Id → X-Org-Id in docs, auth utils, tests and client API calls. - Brand Engine integration: add docs, presets, preview/previewTheme tests and persistence/handler plumbing; remove runtime registerBuiltInThemes calls and rely on server-resolved themes. - Provider & UI updates: BrandProvider now accepts appId; Providers refactored to provide APP_ID and global store; BrandThemeApplicator & ProviderTheme simplified to use pre-resolved themes and added safe guards/warnings. - Brand API: replace raw fetch helpers with api client usage, add brandKitApi/brandConfigApi/layoutApi helpers, and support uploads and SSR-friendly brand config fetch with X-App-Id header. - Admin/UI: BrandKitThemeTab fetches presets from API, auto-applies generated palettes, expands presets into tokensJson for preview; Admin pages updated to use appId semantics and fix uniqueness checks for posts scoped to appId (explicit null handling). - Worker/SSR: cloudflare worker import reordering, brand HTML injection now uses server-provided resolved themes (light/dark), and DB/queue/shortlink routing imports reorganized. - Provisioning: ensure system-level (appId=null) default BrandKit and route mappings are created during user provisioning. - Cache docs: document per-kit surgical invalidation and cache key formats for brand engine. - Misc: tweak useSession clearing logic to preserve valid stored sessions; update tests and small TypeScript/typing adjustments. These changes centralize brand theme resolution on the server, add app-level theming APIs, improve cache invalidation granularity, and standardize the tenant header name. #major change, better logic * feat: add FeatureHighlight, About, Contact, Timeline sections for Atlas and Mono themes Add four new section types (feature-highlight, about, contact, timeline) with full type definitions, themed components for both Atlas and Mono, and integration into the ottalanding theme system. https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * feat: add SaaS theme — third landing page template with modern, airy design - Create 15 SaaS ui-marketing components (Hero, Features, Pricing, Testimonials, FAQ, LogoCloud, CTA, Navbar, Stats, Steps, Footer, FeatureHighlight, About, Contact, Timeline) with pill buttons, soft shadows, rounded-2xl cards - Add SaaS ottalanding theme definition and register in initOttaLanding() - Add 4 new section types to Mono theme (feature-highlight, about, contact, timeline) - Update theme gallery preview to include Atlas/Mono/SaaS template toggle - Add SaaS exports to ui-marketing package.json and tsup.config.ts https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * feat: add landing page admin panel with full CRUD for sites, pages, and sections - Enable ottalanding package in migration config and DB schema - Add section field definitions for 4 new section types (feature-highlight, about, contact, timeline) to support structured admin forms - Create landing admin pages: - Sites list with create/delete - Site editor with settings tab and pages tab - Page editor with sections management (add, reorder, toggle visibility, delete) - Section content editor with type-specific forms powered by SECTION_FIELDS - Add 4 admin routes (/admin/landing, sites/$siteId, pages/$pageId, sections/$sectionId) - Add Landing Pages card to admin index dashboard - Create landingHooks.ts with typed CRUD hooks for all ottalanding entities https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * fix: add missing Atlas/Mono build entries and ottalanding dependency - Add FeatureHighlight, About, Contact, Timeline tsup entries and package.json exports for Atlas and Mono themes (were only added for SaaS) - Add @ottabase/ottalanding as dependency in tanstack app (fixes worker test) - Fix ottalanding test script to pass with no test files https://claude.ai/code/session_015rv1ZTZ9tsTEkBrE1WtxVK * Register ottalanding models with RLS Import and register ottalanding models in the worker DB init, and add AppScoped RLS entries for ottalanding_sites, ottalanding_pages, and ottalanding_sections. Add static writable definitions to LandingPage and LandingSection so parent FKs (siteId/pageId) are create-only and appId can be injected by RLS; update updateable fields accordingly. These changes enable proper row-level security, app scoping, and controlled write permissions for landing site/page/section records. * Add landing preview and homepage support Add a LandingPreviewModal for rendering a live preview of a landing site and pages (uses @ottalanding renderPage). Wire the preview into the AdminLandingSiteEditorPage (Preview button, dialog) and export it from the landing index. Introduce homePageId everywhere: add column to landingSites schema, expose it in the LandingSite model UI, and add homePageId to the LandingSiteItem type. Use homePageId (fallback to slug "home" or first page) when choosing which page to preview. Harden list handling with Array.isArray guards for API responses and add logic to fetch sections per page for the preview. Also update next-env reference path and add a dev:homepage script to package.json. --------- Co-authored-by: Claude <noreply@anthropic.com>
|
ENHANCED FROM THIS BRANCH. STALE NOW. SO |
Implements 16 marketing section components across two distinct templates:
Atlas (GitHub/Atlassian/Notion style):
Mono (Linear/Vercel style):
All components consume brand CSS variables via Tailwind token classes (bg-background, text-foreground, border-border, etc.) — zero hardcoded colors, fully compatible with @ottabase/brand-engine themes.
https://claude.ai/code/session_01R1MVKojSQdAqQZUT4U6Mm7