-
-
-
-
- The leading SaaS Starter Kit for ambitious developers
-
-
-
-
- The ultimate SaaS Starter
-
- for your next project
-
-
-
-
- Build and Ship a SaaS faster than ever before with the
- next-gen SaaS Starter Kit. Ship your SaaS in days, not months.
-
-
-
-
-
-
-
-
-
+
+ The leading SaaS Starter Kit for ambitious developers
+
+ }
+ title={
+ <>
+ The ultimate SaaS Starter
+ for your next project
+ >
+ }
+ subtitle={
+
+ Build and Ship a SaaS faster than ever before with the next-gen SaaS
+ Starter Kit. Ship your SaaS in days, not months.
+
+ }
+ cta={ }
+ image={
-
-
+ }
+ />
-
-
-
-
-
-
- SaaS Starter Kit
-
-
-
-
-
- The ultimate SaaS Starter Kit
-
- .{' '}
-
- Unleash your creativity and build your SaaS faster than
- ever with Makerkit.
-
-
-
-
-
-
-
+
+
+ The ultimate SaaS Starter Kit
+
+ .{' '}
+
+ Unleash your creativity and build your SaaS faster than ever
+ with Makerkit.
+
+ >
+ }
+ icon={
+
+
+ All-in-one solution
+
+ }
+ >
+
-
-
+
+
@@ -193,24 +154,11 @@ function Home() {
'flex flex-col items-center justify-center space-y-16 py-16'
}
>
-
-
Get started for free. No credit card required.
-
-
-
- Fair pricing for all types of businesses
-
-
-
- Get started on our free plan and upgrade when you are ready.
-
-
-
+
Get started for free. No credit card required.}
+ heading="Fair pricing for all types of businesses"
+ subheading="Get started on our free plan and upgrade when you are ready."
+ />
- {children}
-
- );
-}
-
-function Pill(
- props: React.PropsWithChildren<{
- new?: boolean;
- }>,
-) {
- return (
-
- {props.new && (
-
- New
-
- )}
- {props.children}
-
- );
-}
-
-function FeatureShowcaseContainer(props: React.PropsWithChildren) {
- return (
-
- {props.children}
-
- );
-}
-
-function FeatureContainer(
- props: React.PropsWithChildren<{
- className?: string;
- reverse?: boolean;
- }>,
-) {
- return (
-
- {props.children}
-
- );
-}
-
function MainCallToActionButton() {
return (
-
+
@@ -318,84 +195,13 @@ function MainCallToActionButton() {
/>
-
+
-
+
-
+
);
}
-
-function IconContainer(
- props: React.PropsWithChildren<{
- className?: string;
- }>,
-) {
- return (
-
- );
-}
-
-function FeatureCard(
- props: React.PropsWithChildren<{
- title: string;
- description: string;
- className?: string;
- }>,
-) {
- return (
-
-
- {props.title}
-
-
- {props.description}
-
-
-
- {props.children}
-
- );
-}
-
-function GradientSecondaryText(
- props: React.PropsWithChildren<{
- className?: string;
- }>,
-) {
- return (
-
- {props.children}
-
- );
-}
diff --git a/packages/analytics/README.md b/packages/analytics/README.md
new file mode 100644
index 000000000..edf1601a1
--- /dev/null
+++ b/packages/analytics/README.md
@@ -0,0 +1,10 @@
+# Analytics - @kit/analytics
+
+@kit/analytics Package provides a simple and consistent API for tracking analytics events in web applications.
+
+## Overview
+
+This version of the `@kit/analytics` package uses classes internally for structure and encapsulation, but exposes a functional API for ease of use and consistency with Makerkit's style.
+
+## Implementation
+
diff --git a/packages/analytics/package.json b/packages/analytics/package.json
new file mode 100644
index 000000000..150582fbc
--- /dev/null
+++ b/packages/analytics/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@kit/analytics",
+ "private": true,
+ "version": "0.1.0",
+ "scripts": {
+ "clean": "git clean -xdf .turbo node_modules",
+ "format": "prettier --check \"**/*.{ts,tsx}\"",
+ "lint": "eslint .",
+ "typecheck": "tsc --noEmit"
+ },
+ "prettier": "@kit/prettier-config",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "devDependencies": {
+ "@kit/eslint-config": "workspace:*",
+ "@kit/prettier-config": "workspace:*",
+ "@kit/tailwind-config": "workspace:*",
+ "@kit/tsconfig": "workspace:*"
+ },
+ "eslintConfig": {
+ "root": true,
+ "extends": [
+ "@kit/eslint-config/base",
+ "@kit/eslint-config/react"
+ ]
+ },
+ "typesVersions": {
+ "*": {
+ "*": [
+ "src/*"
+ ]
+ }
+ }
+}
diff --git a/packages/analytics/src/analytics-manager.ts b/packages/analytics/src/analytics-manager.ts
new file mode 100644
index 000000000..ed21feae8
--- /dev/null
+++ b/packages/analytics/src/analytics-manager.ts
@@ -0,0 +1,72 @@
+import { NullAnalyticsService } from './null-analytics-service';
+import {
+ AnalyticsManager,
+ AnalyticsService,
+ CreateAnalyticsManagerOptions,
+} from './types';
+
+/**
+ * Creates an analytics manager that can be used to track page views and events. The manager is initialized with a
+ * default provider and can be switched to a different provider at any time. The manager will use a NullAnalyticsService
+ * if the provider is not registered.
+ * @param options
+ */
+export function createAnalyticsManager(
+ options: CreateAnalyticsManagerOptions,
+): AnalyticsManager {
+ let activeService: AnalyticsService = NullAnalyticsService;
+
+ const getActiveService = (): AnalyticsService => {
+ if (activeService === NullAnalyticsService) {
+ console.warn(
+ 'Analytics service not initialized. Using NullAnalyticsService.',
+ );
+ }
+
+ return activeService;
+ };
+
+ const initialize = (provider: T, config: Config) => {
+ const factory = options.providers[provider];
+
+ if (!factory) {
+ console.error(
+ `Analytics provider '${provider}' not registered. Using NullAnalyticsService.`,
+ );
+
+ activeService = NullAnalyticsService;
+ return;
+ }
+
+ activeService = factory(config);
+ activeService.initialize();
+ };
+
+ // Initialize with the default provider
+ initialize(options.defaultProvider, {} as Config);
+
+ return {
+ identify: (userId: string, traits?: Record) => {
+ return getActiveService().identify(userId, traits);
+ },
+
+ /**
+ * Track a page view with the given URL.
+ * @param url
+ */
+ trackPageView: (url: string) => {
+ return getActiveService().trackPageView(url);
+ },
+ /**
+ * Track an event with the given name and properties.
+ * @param eventName
+ * @param eventProperties
+ */
+ trackEvent: (
+ eventName: string,
+ eventProperties?: Record,
+ ) => {
+ return getActiveService().trackEvent(eventName, eventProperties);
+ },
+ };
+}
diff --git a/packages/analytics/src/index.ts b/packages/analytics/src/index.ts
new file mode 100644
index 000000000..74a47eda6
--- /dev/null
+++ b/packages/analytics/src/index.ts
@@ -0,0 +1,10 @@
+import { createAnalyticsManager } from './analytics-manager';
+import { NullAnalyticsService } from './null-analytics-service';
+import type { AnalyticsManager } from './types';
+
+export const analytics: AnalyticsManager = createAnalyticsManager({
+ defaultProvider: 'null',
+ providers: {
+ null: () => NullAnalyticsService,
+ },
+});
diff --git a/packages/analytics/src/null-analytics-service.ts b/packages/analytics/src/null-analytics-service.ts
new file mode 100644
index 000000000..72d1ed658
--- /dev/null
+++ b/packages/analytics/src/null-analytics-service.ts
@@ -0,0 +1,16 @@
+import { AnalyticsService } from './types';
+
+const noop = () => {
+ // do nothing - this is to prevent errors when the analytics service is not initialized
+};
+
+/**
+ * Null analytics service that does nothing. It is initialized with a noop function. This is useful for testing or when
+ * the user is calling analytics methods before the analytics service is initialized.
+ */
+export const NullAnalyticsService: AnalyticsService = {
+ initialize: noop,
+ trackPageView: noop,
+ trackEvent: noop,
+ identify: noop,
+};
diff --git a/packages/analytics/src/types.ts b/packages/analytics/src/types.ts
new file mode 100644
index 000000000..194b9fb4f
--- /dev/null
+++ b/packages/analytics/src/types.ts
@@ -0,0 +1,32 @@
+interface TrackEvent {
+ trackEvent(
+ eventName: string,
+ eventProperties?: Record,
+ ): void;
+}
+
+interface TrackPageView {
+ trackPageView(url: string): void;
+}
+
+interface Identify {
+ identify(userId: string, traits?: Record): void;
+}
+
+export interface AnalyticsService extends TrackPageView, TrackEvent, Identify {
+ initialize(): void;
+}
+
+export type AnalyticsProviderFactory = (
+ config: Config,
+) => AnalyticsService;
+
+export interface CreateAnalyticsManagerOptions<
+ T extends string,
+ Config extends object,
+> {
+ defaultProvider: T;
+ providers: Record>;
+}
+
+export interface AnalyticsManager extends TrackPageView, TrackEvent, Identify {}
diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json
new file mode 100644
index 000000000..c4697e934
--- /dev/null
+++ b/packages/analytics/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@kit/tsconfig/base.json",
+ "compilerOptions": {
+ "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
+ },
+ "include": ["*.ts", "src"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/billing/gateway/src/components/pricing-table.tsx b/packages/billing/gateway/src/components/pricing-table.tsx
index 5af8d9cc7..8c715a455 100644
--- a/packages/billing/gateway/src/components/pricing-table.tsx
+++ b/packages/billing/gateway/src/components/pricing-table.tsx
@@ -189,7 +189,7 @@ function PricingItem(
{children}
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 22d4df29d..afc06e9e9 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -121,7 +121,8 @@
"./cookie-banner": "./src/makerkit/cookie-banner.tsx",
"./card-button": "./src/makerkit/card-button.tsx",
"./version-updater": "./src/makerkit/version-updater.tsx",
- "./multi-step-form": "./src/makerkit/multi-step-form.tsx"
+ "./multi-step-form": "./src/makerkit/multi-step-form.tsx",
+ "./marketing": "./src/makerkit/marketing/index.tsx"
},
"typesVersions": {
"*": {
diff --git a/packages/ui/src/makerkit/marketing/cta-button.tsx b/packages/ui/src/makerkit/marketing/cta-button.tsx
new file mode 100644
index 000000000..c5c704312
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/cta-button.tsx
@@ -0,0 +1,23 @@
+import { forwardRef } from 'react';
+
+import { Button } from '../../shadcn/button';
+import { cn } from '../../utils';
+
+export const CtaButton = forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(function CtaButtonComponent({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/feature-card.tsx b/packages/ui/src/makerkit/marketing/feature-card.tsx
new file mode 100644
index 000000000..66d16a4c7
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/feature-card.tsx
@@ -0,0 +1,44 @@
+import React, { forwardRef } from 'react';
+
+import {
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '../../shadcn/card';
+import { cn } from '../../utils';
+
+interface FeatureCardProps extends React.HTMLAttributes {
+ label: string;
+ description: string;
+ image?: React.ReactNode;
+}
+
+export const FeatureCard = forwardRef(
+ function FeatureCardComponent(
+ { className, label, description, image, children, ...props },
+ ref,
+ ) {
+ return (
+
+
+ {label}
+
+ {description}
+
+
+
+ {image}
+ {children}
+
+
+ );
+ },
+);
diff --git a/packages/ui/src/makerkit/marketing/feature-grid.tsx b/packages/ui/src/makerkit/marketing/feature-grid.tsx
new file mode 100644
index 000000000..c7947271b
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/feature-grid.tsx
@@ -0,0 +1,21 @@
+import React, { forwardRef } from 'react';
+
+import { cn } from '../../utils';
+
+export const FeatureGrid = forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(function FeatureGridComponent({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/feature-showcase.tsx b/packages/ui/src/makerkit/marketing/feature-showcase.tsx
new file mode 100644
index 000000000..33a2405ab
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/feature-showcase.tsx
@@ -0,0 +1,50 @@
+import React, { forwardRef } from 'react';
+
+import { cn } from '../../utils';
+
+interface FeatureShowcaseProps extends React.HTMLAttributes {
+ heading: React.ReactNode;
+ icon?: React.ReactNode;
+}
+
+export const FeatureShowcase = forwardRef(
+ function FeatureShowcaseComponent(
+ { className, heading, icon, children, ...props },
+ ref,
+ ) {
+ return (
+
+
+ {icon &&
{icon}
}
+
+ {heading}
+
+
+ {children}
+
+ );
+ },
+);
+
+export function FeatureShowcaseIconContainer(
+ props: React.PropsWithChildren<{
+ className?: string;
+ }>,
+) {
+ return (
+
+ );
+}
diff --git a/packages/ui/src/makerkit/marketing/footer.tsx b/packages/ui/src/makerkit/marketing/footer.tsx
new file mode 100644
index 000000000..7dcde1740
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/footer.tsx
@@ -0,0 +1,95 @@
+import { forwardRef } from 'react';
+
+import { cn } from '../../utils';
+
+interface FooterSection {
+ heading: React.ReactNode;
+ links: Array<{
+ href: string;
+ label: React.ReactNode;
+ }>;
+}
+
+interface FooterProps extends React.HTMLAttributes {
+ logo: React.ReactNode;
+ description: React.ReactNode;
+ copyright: React.ReactNode;
+ sections: FooterSection[];
+}
+
+export const Footer = forwardRef(
+ function MarketingFooterComponent(
+ { className, logo, description, copyright, sections, ...props },
+ ref,
+ ) {
+ return (
+
+
+
+
+
+
+ {sections.map((section, index) => (
+
+
+
+ {section.heading}
+
+
+
+ {section.links.map((link, linkIndex) => (
+
+ {link.label}
+
+ ))}
+
+
+
+ ))}
+
+
+
+
+ );
+ },
+);
+
+function FooterSectionHeading(props: React.PropsWithChildren) {
+ return {props.children} ;
+}
+
+function FooterSectionList(props: React.PropsWithChildren) {
+ return ;
+}
+
+function FooterLink({
+ href,
+ children,
+}: React.PropsWithChildren<{ href: string }>) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/ui/src/makerkit/marketing/gradient-secondary-text.tsx b/packages/ui/src/makerkit/marketing/gradient-secondary-text.tsx
new file mode 100644
index 000000000..46dc26cd4
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/gradient-secondary-text.tsx
@@ -0,0 +1,27 @@
+import { forwardRef } from 'react';
+
+import { Slot, Slottable } from '@radix-ui/react-slot';
+
+import { cn } from '../../utils';
+
+export const GradientSecondaryText = forwardRef<
+ HTMLSpanElement,
+ React.HTMLAttributes & {
+ asChild?: boolean;
+ }
+>(function GradientSecondaryTextComponent({ className, ...props }, ref) {
+ const Comp = props.asChild ? Slot : 'span';
+
+ return (
+
+ {props.children}
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/gradient-text.tsx b/packages/ui/src/makerkit/marketing/gradient-text.tsx
new file mode 100644
index 000000000..1707040fa
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/gradient-text.tsx
@@ -0,0 +1,21 @@
+import React, { forwardRef } from 'react';
+
+import { cn } from '../../utils';
+
+export const GradientText = forwardRef<
+ HTMLSpanElement,
+ React.HTMLAttributes
+>(function GradientTextComponent({ className, children, ...props }, ref) {
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/header.tsx b/packages/ui/src/makerkit/marketing/header.tsx
new file mode 100644
index 000000000..e970790d8
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/header.tsx
@@ -0,0 +1,37 @@
+import { forwardRef } from 'react';
+
+import { cn } from '../../utils';
+
+interface HeaderProps extends React.HTMLAttributes {
+ logo?: React.ReactNode;
+ navigation?: React.ReactNode;
+ actions?: React.ReactNode;
+}
+
+export const Header = forwardRef(
+ function MarketingHeaderComponent(
+ { className, logo, navigation, actions, ...props },
+ ref,
+ ) {
+ return (
+
+
+
+
{logo}
+
{navigation}
+
+ {actions}
+
+
+
+
+ );
+ },
+);
diff --git a/packages/ui/src/makerkit/marketing/hero-title.tsx b/packages/ui/src/makerkit/marketing/hero-title.tsx
new file mode 100644
index 000000000..94352d699
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/hero-title.tsx
@@ -0,0 +1,27 @@
+import { forwardRef } from 'react';
+
+import { Slot, Slottable } from '@radix-ui/react-slot';
+
+import { cn } from '../../utils';
+
+export const HeroTitle = forwardRef<
+ HTMLHeadingElement,
+ React.HTMLAttributes & {
+ asChild?: boolean;
+ }
+>(function HeroTitleComponent({ children, className, ...props }, ref) {
+ const Comp = props.asChild ? Slot : 'h1';
+
+ return (
+
+ {children}
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/hero.tsx b/packages/ui/src/makerkit/marketing/hero.tsx
new file mode 100644
index 000000000..8564f6f9e
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/hero.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+
+import { Heading } from '../../shadcn/heading';
+import { cn } from '../../utils';
+import { HeroTitle } from './hero-title';
+
+interface HeroProps {
+ pill?: React.ReactNode;
+ title: React.ReactNode;
+ subtitle?: React.ReactNode;
+ cta?: React.ReactNode;
+ image?: React.ReactNode;
+ className?: string;
+ animate?: boolean;
+}
+
+export function Hero({
+ pill,
+ title,
+ subtitle,
+ cta,
+ image,
+ className,
+ animate = true,
+}: HeroProps) {
+ return (
+
+
+
+ {pill && (
+
+ {pill}
+
+ )}
+
+
+
{title}
+
+ {subtitle && (
+
+
+ {subtitle}
+
+
+ )}
+
+
+ {cta && (
+
+ {cta}
+
+ )}
+
+
+
+ {image && (
+
+ {image}
+
+ )}
+
+ );
+}
diff --git a/packages/ui/src/makerkit/marketing/index.tsx b/packages/ui/src/makerkit/marketing/index.tsx
new file mode 100644
index 000000000..7a648f031
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/index.tsx
@@ -0,0 +1,12 @@
+export * from './hero-title';
+export * from './pill';
+export * from './gradient-secondary-text';
+export * from './gradient-text';
+export * from './hero';
+export * from './secondary-hero';
+export * from './cta-button';
+export * from './header';
+export * from './footer';
+export * from './feature-showcase';
+export * from './feature-grid';
+export * from './feature-card';
diff --git a/packages/ui/src/makerkit/marketing/pill.tsx b/packages/ui/src/makerkit/marketing/pill.tsx
new file mode 100644
index 000000000..2a3e7d718
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/pill.tsx
@@ -0,0 +1,40 @@
+import { forwardRef } from 'react';
+
+import { Slot, Slottable } from '@radix-ui/react-slot';
+
+import { cn } from '../../utils';
+import { GradientSecondaryText } from './gradient-secondary-text';
+
+export const Pill = forwardRef<
+ HTMLHeadingElement,
+ React.HTMLAttributes & {
+ label?: string;
+ asChild?: boolean;
+ }
+>(function PillComponent({ className, asChild, ...props }, ref) {
+ const Comp = asChild ? Slot : 'h3';
+
+ return (
+
+ {props.label && (
+
+ {props.label}
+
+ )}
+
+ {props.children}
+
+
+ );
+});
diff --git a/packages/ui/src/makerkit/marketing/secondary-hero.tsx b/packages/ui/src/makerkit/marketing/secondary-hero.tsx
new file mode 100644
index 000000000..abb95c6e9
--- /dev/null
+++ b/packages/ui/src/makerkit/marketing/secondary-hero.tsx
@@ -0,0 +1,49 @@
+import { forwardRef } from 'react';
+
+import { Heading } from '../../shadcn/heading';
+import { cn } from '../../utils';
+
+interface SecondaryHeroProps extends React.HTMLAttributes {
+ pill?: React.ReactNode;
+ heading: React.ReactNode;
+ subheading: React.ReactNode;
+}
+
+export const SecondaryHero = forwardRef(
+ function SecondaryHeroComponent(
+ { className, pill, heading, subheading, children, ...props },
+ ref,
+ ) {
+ return (
+
+ {pill && (
+
+ {pill}
+
+ )}
+
+
+
+ {heading}
+
+
+
+ {subheading}
+
+
+
+ {children}
+
+ );
+ },
+);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e9be990df..e3f1f34f1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -218,6 +218,21 @@ importers:
specifier: ^5.5.3
version: 5.5.3
+ packages/analytics:
+ devDependencies:
+ '@kit/eslint-config':
+ specifier: workspace:*
+ version: link:../../tooling/eslint
+ '@kit/prettier-config':
+ specifier: workspace:*
+ version: link:../../tooling/prettier
+ '@kit/tailwind-config':
+ specifier: workspace:*
+ version: link:../../tooling/tailwind
+ '@kit/tsconfig':
+ specifier: workspace:*
+ version: link:../../tooling/typescript
+
packages/billing/core:
devDependencies:
'@kit/eslint-config':