├── apps
├── webapp
│ ├── .gitignore
│ ├── postcss.config.mjs
│ ├── src
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── app
│ │ │ ├── (authenticated)
│ │ │ │ ├── (core)
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── programs
│ │ │ │ │ │ └── [id]
│ │ │ │ │ │ │ └── layout.tsx
│ │ │ │ │ ├── rewards
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── activity
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── participants
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── analytics
│ │ │ │ │ │ └── page.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── settings
│ │ │ │ │ ├── organization
│ │ │ │ │ │ ├── general
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ ├── api-keys
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── members
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ ├── product
│ │ │ │ │ │ ├── secrets
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── general
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── account
│ │ │ │ │ │ └── profile
│ │ │ │ │ │ └── page.tsx
│ │ │ │ └── onboarding
│ │ │ │ │ └── layout.tsx
│ │ │ ├── api
│ │ │ │ ├── auth
│ │ │ │ │ └── [...all]
│ │ │ │ │ │ └── route.ts
│ │ │ │ └── trpc
│ │ │ │ │ └── [trpc]
│ │ │ │ │ └── route.ts
│ │ │ ├── auth
│ │ │ │ └── layout.tsx
│ │ │ └── accept-invitation
│ │ │ │ └── [token]
│ │ │ │ └── page.tsx
│ │ ├── lib
│ │ │ ├── utils.ts
│ │ │ ├── logger.ts
│ │ │ ├── hooks
│ │ │ │ └── use-debounce.ts
│ │ │ ├── auth.ts
│ │ │ ├── forms
│ │ │ │ └── notification-config-schema.ts
│ │ │ ├── auth-client.ts
│ │ │ └── date-utils.ts
│ │ ├── server
│ │ │ ├── db.ts
│ │ │ └── api
│ │ │ │ └── routers
│ │ │ │ ├── post.ts
│ │ │ │ ├── product-secrets.ts
│ │ │ │ └── referral.ts
│ │ ├── hooks
│ │ │ └── use-mobile.ts
│ │ ├── trpc
│ │ │ ├── query-client.ts
│ │ │ └── server.ts
│ │ └── components
│ │ │ ├── date-display.tsx
│ │ │ ├── providers
│ │ │ ├── auth-ui-provider.tsx
│ │ │ └── posthog-provider.tsx
│ │ │ ├── site-header.tsx
│ │ │ ├── program-setup-card.tsx
│ │ │ ├── referral-widget-init.tsx
│ │ │ ├── nav-secondary.tsx
│ │ │ ├── preview-pane.tsx
│ │ │ └── referral-widget-preview.tsx
│ ├── eslint.config.mjs
│ ├── vitest.config.ts
│ ├── components.json
│ ├── tsconfig.json
│ ├── instrumentation-client.ts
│ └── next.config.ts
├── refer
│ ├── .gitignore
│ ├── vitest.config.ts
│ ├── tsconfig.json
│ ├── .env.example
│ ├── src
│ │ ├── index.ts
│ │ └── routes
│ │ │ └── health.ts
│ ├── test
│ │ ├── utils
│ │ │ └── testServer.ts
│ │ └── setup.ts
│ └── package.json
├── www
│ ├── content
│ │ └── docs
│ │ │ ├── meta.json
│ │ │ ├── api
│ │ │ ├── index.mdx
│ │ │ ├── meta.json
│ │ │ ├── get-programs.mdx
│ │ │ ├── get-participants.mdx
│ │ │ ├── get-program-by-id.mdx
│ │ │ └── get-participant-by-id.mdx
│ │ │ └── platform
│ │ │ ├── contributing.mdx
│ │ │ ├── paddle.mdx
│ │ │ ├── index.mdx
│ │ │ ├── fraud-prevention.mdx
│ │ │ ├── projects.mdx
│ │ │ ├── newsletter.mdx
│ │ │ ├── meta.json
│ │ │ ├── sponsoring.mdx
│ │ │ ├── participants.mdx
│ │ │ └── setup-affiliate-program.mdx
│ ├── public
│ │ ├── favicon.ico
│ │ ├── refref-icon.png
│ │ ├── webapp-home-1.png
│ │ ├── refref-logo-dark.png
│ │ ├── refref-logo-light.png
│ │ ├── refref-logo-dark-gs.png
│ │ ├── refref-logo-dark@2x.png
│ │ ├── refref-logo-dark@3x.png
│ │ ├── refref-logo-light-gs.png
│ │ ├── refref-logo-light@2x.png
│ │ ├── refref-logo-light@3x.png
│ │ ├── refref-text-logo-dark.png
│ │ ├── refref-logo-dark-gs@2x.png
│ │ ├── refref-logo-dark-gs@3x.png
│ │ ├── refref-logo-light-gs@2x.png
│ │ ├── refref-logo-light-gs@3x.png
│ │ ├── refref-text-logo-light.png
│ │ ├── github-readme-header-dark.png
│ │ ├── github-readme-header-light.png
│ │ ├── refref-github-repo-cover.png
│ │ ├── refref-text-logo-dark@2x.png
│ │ ├── refref-text-logo-dark@3x.png
│ │ ├── refref-text-logo-light@2x.png
│ │ ├── refref-text-logo-light@3x.png
│ │ ├── refref-github-repo-cover-dark-transparent.png
│ │ └── refref-github-repo-cover-light-transparent.png
│ ├── app
│ │ ├── robots.txt
│ │ ├── (home)
│ │ │ ├── docs
│ │ │ │ └── page.tsx
│ │ │ ├── contact
│ │ │ │ └── page.tsx
│ │ │ ├── changelog
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── page.tsx
│ │ │ └── blog
│ │ │ │ └── [slug]
│ │ │ │ └── page.client.tsx
│ │ ├── community
│ │ │ └── page.tsx
│ │ ├── github
│ │ │ └── page.tsx
│ │ ├── api
│ │ │ └── search
│ │ │ │ └── route.ts
│ │ ├── docs
│ │ │ ├── layout.tsx
│ │ │ └── [...slug]
│ │ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── postcss.config.mjs
│ ├── lib
│ │ ├── cn.ts
│ │ ├── utils.ts
│ │ ├── source.ts
│ │ └── metadata.ts
│ ├── .env.example
│ ├── next.config.mjs
│ ├── scripts
│ │ └── generate-docs.mjs
│ ├── tsconfig.json
│ ├── components.json
│ ├── .gitignore
│ ├── source.config.ts
│ ├── components
│ │ └── ui
│ │ │ ├── label.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── input.tsx
│ │ │ ├── animated-gradient-text.tsx
│ │ │ └── checkbox.tsx
│ └── README.md
├── api
│ ├── .gitignore
│ ├── api.http
│ ├── .env.example
│ ├── src
│ │ ├── routes
│ │ │ ├── v1
│ │ │ │ ├── track.ts
│ │ │ │ └── programs.ts
│ │ │ ├── openapi.ts
│ │ │ └── health.ts
│ │ └── index.ts
│ ├── vitest.config.ts
│ ├── tsconfig.json
│ ├── test
│ │ ├── utils
│ │ │ └── testServer.ts
│ │ └── setup.ts
│ ├── README.md
│ └── package.json
├── acme
│ ├── next.config.js
│ ├── next-env.d.ts
│ ├── package.json
│ ├── tsconfig.json
│ └── src
│ │ ├── app
│ │ ├── layout.tsx
│ │ └── api
│ │ │ ├── logout
│ │ │ └── route.ts
│ │ │ ├── test
│ │ │ └── reset
│ │ │ │ └── route.ts
│ │ │ └── me
│ │ │ └── route.ts
│ │ └── lib
│ │ ├── refref-events.ts
│ │ └── refref-config.ts
├── assets
│ ├── .gitignore
│ ├── wrangler.toml
│ ├── tsconfig.json
│ └── package.json
└── e2e
│ ├── sanity
│ └── pages
│ │ ├── acme
│ │ ├── acme-base-page.ts
│ │ ├── acme-signup-page.ts
│ │ ├── acme-dashboard-page.ts
│ │ └── acme-purchase-page.ts
│ │ └── base-page.ts
│ ├── scripts
│ └── cleanup-ports.sh
│ ├── tsconfig.json
│ ├── package.json
│ └── .env.example
├── packages
├── widget
│ ├── src
│ │ ├── vite-env.d.ts
│ │ ├── lib
│ │ │ ├── utils.ts
│ │ │ ├── store.ts
│ │ │ └── config.ts
│ │ ├── main.tsx
│ │ ├── index.css
│ │ └── widget
│ │ │ └── components
│ │ │ └── widget-container.tsx
│ ├── eslint.config.mjs
│ ├── sample.env
│ ├── index.html
│ ├── tsconfig.json
│ └── components.json
├── eslint-config
│ ├── README.md
│ ├── package.json
│ ├── base.js
│ ├── react-internal.js
│ └── next.js
├── types
│ ├── tsconfig.json
│ ├── eslint.config.mjs
│ ├── tsup.config.ts
│ ├── src
│ │ ├── event-config.ts
│ │ └── program-template-steps.ts
│ └── package.json
├── attribution-script
│ ├── eslint.config.mjs
│ ├── index.html
│ ├── vitest.config.ts
│ ├── tsconfig.json
│ ├── src
│ │ ├── constants.ts
│ │ └── types.ts
│ ├── vite.config.ts
│ └── package.json
├── ui
│ ├── postcss.config.mjs
│ ├── src
│ │ ├── lib
│ │ │ ├── utils.ts
│ │ │ ├── format.ts
│ │ │ └── id.ts
│ │ ├── components
│ │ │ ├── aspect-ratio.tsx
│ │ │ ├── skeleton.tsx
│ │ │ ├── spinner.tsx
│ │ │ ├── label.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── textarea.tsx
│ │ │ ├── progress.tsx
│ │ │ ├── collapsible.tsx
│ │ │ ├── kbd.tsx
│ │ │ ├── input.tsx
│ │ │ ├── data-table
│ │ │ │ └── data-table-advanced-toolbar.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── referral-widget
│ │ │ │ └── referral-widget-dialog-trigger.tsx
│ │ │ ├── switch.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── checkbox.tsx
│ │ │ ├── radio-group.tsx
│ │ │ └── hover-card.tsx
│ │ ├── hooks
│ │ │ ├── use-mobile.ts
│ │ │ ├── use-callback-ref.ts
│ │ │ └── use-debounced-callback.ts
│ │ └── types
│ │ │ └── data-table.ts
│ ├── tsconfig.json
│ └── components.json
├── typescript-config
│ ├── package.json
│ ├── react-library.json
│ ├── nextjs.json
│ └── base.json
├── auth
│ ├── tsconfig.json
│ └── package.json
├── refref-better-auth
│ ├── tsconfig.json
│ ├── vitest.config.ts
│ ├── build.config.ts
│ ├── src
│ │ ├── index.ts
│ │ ├── types.test.ts
│ │ └── utils
│ │ │ └── cookie.test.ts
│ └── package.json
├── id
│ ├── tsconfig.json
│ └── package.json
├── utils
│ ├── tsconfig.json
│ ├── src
│ │ ├── index.ts
│ │ └── plugins
│ │ │ └── coredb.ts
│ ├── package.json
│ └── README.md
├── email-templates
│ ├── tsconfig.json
│ ├── src
│ │ ├── utils
│ │ │ └── render.ts
│ │ └── index.ts
│ └── package.json
└── coredb
│ ├── drizzle.config.ts
│ ├── drizzle
│ ├── meta
│ │ └── _journal.json
│ └── 0001_flaky_madelyne_pryor.sql
│ ├── tsconfig.json
│ ├── src
│ └── index.ts
│ └── package.json
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .npmrc
├── pnpm-workspace.yaml
├── .gitignore
├── package.json
├── turbo.json
├── docker-compose.yml
└── .dockerignore
/apps/webapp/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 |
--------------------------------------------------------------------------------
/apps/refer/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .env
4 | .env.local
5 |
--------------------------------------------------------------------------------
/apps/www/content/docs/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": ["platform", "api"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/widget/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/api/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | .env
4 | .env.local
5 | *.log
6 |
--------------------------------------------------------------------------------
/apps/webapp/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export { default } from "@refref/ui/postcss.config";
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: refrefhq
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | public-hoist-pattern[]=*better-auth*
2 | public-hoist-pattern[]=*@better-auth*
3 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "apps/*"
3 | - "!apps/www"
4 | - "packages/*"
5 |
--------------------------------------------------------------------------------
/apps/www/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/favicon.ico
--------------------------------------------------------------------------------
/apps/www/public/refref-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-icon.png
--------------------------------------------------------------------------------
/apps/www/public/webapp-home-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/webapp-home-1.png
--------------------------------------------------------------------------------
/packages/eslint-config/README.md:
--------------------------------------------------------------------------------
1 | # `@turbo/eslint-config`
2 |
3 | Collection of internal eslint configurations.
4 |
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark.png
--------------------------------------------------------------------------------
/apps/www/app/robots.txt:
--------------------------------------------------------------------------------
1 | User-Agent: *
2 | Allow: /
3 | Disallow: /private/
4 |
5 | Sitemap: https://refref.ai/sitemap.xml
6 |
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light.png
--------------------------------------------------------------------------------
/apps/www/content/docs/api/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: RefRef API
3 | description: RefRef API documentation
4 | index: true
5 | ---
6 |
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark-gs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark-gs.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark@3x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light-gs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light-gs.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light@3x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-dark.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark-gs@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark-gs@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-dark-gs@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-dark-gs@3x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light-gs@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light-gs@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-logo-light-gs@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-logo-light-gs@3x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-light.png
--------------------------------------------------------------------------------
/apps/www/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | '@tailwindcss/postcss': {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/apps/www/public/github-readme-header-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/github-readme-header-dark.png
--------------------------------------------------------------------------------
/apps/www/public/github-readme-header-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/github-readme-header-light.png
--------------------------------------------------------------------------------
/apps/www/public/refref-github-repo-cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-github-repo-cover.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-dark@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-dark@3x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-light@2x.png
--------------------------------------------------------------------------------
/apps/www/public/refref-text-logo-light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-text-logo-light@3x.png
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/contributing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Contributing
3 | description: Guide to contributing to the RefRef open source project
4 | ---
5 | TBD
--------------------------------------------------------------------------------
/apps/acme/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/packages/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "include": ["src"],
4 | "exclude": ["node_modules", "dist"]
5 | }
6 |
--------------------------------------------------------------------------------
/apps/api/api.http:
--------------------------------------------------------------------------------
1 | @host = http://localhost:3001
2 |
3 | ### Health Check - Root
4 | GET {{host}}/
5 |
6 | ### Health Check - Health Endpoint
7 | GET {{host}}/health
8 |
--------------------------------------------------------------------------------
/apps/webapp/src/styles/app.css:
--------------------------------------------------------------------------------
1 | @import "@daveyplate/better-auth-ui/css";
2 | @import "@refref/ui/globals.css";
3 |
4 | :root {
5 | --content-max-width: 128rem;
6 | }
7 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/docs/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | export default async function DocsPage() {
4 | redirect("/docs/platform");
5 | }
6 |
--------------------------------------------------------------------------------
/apps/www/public/refref-github-repo-cover-dark-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-github-repo-cover-dark-transparent.png
--------------------------------------------------------------------------------
/packages/types/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@refref/eslint-config/base";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | export default function Home() {
4 | redirect("/programs");
5 | }
6 |
--------------------------------------------------------------------------------
/apps/www/public/refref-github-repo-cover-light-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refrefhq/refref/HEAD/apps/www/public/refref-github-repo-cover-light-transparent.png
--------------------------------------------------------------------------------
/apps/webapp/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { nextJsConfig } from "@refref/eslint-config/next-js";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default nextJsConfig;
5 |
--------------------------------------------------------------------------------
/apps/www/content/docs/api/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "RefRef API",
3 | "description": "RefRef API documentation",
4 | "root": "true",
5 | "icon": "SquareCode"
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/paddle.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Paddle Integration
3 | description: Integrating Paddle with RefRef for payment processing and referral tracking
4 | ---
5 | TBD
--------------------------------------------------------------------------------
/packages/widget/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@refref/eslint-config/react-internal";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | description: Introduction to RefRef
4 | ---
5 | Welcome to RefRef - Open Source Referral Management Platform
6 |
7 |
--------------------------------------------------------------------------------
/packages/attribution-script/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from "@refref/eslint-config/base";
2 |
3 | /** @type {import("eslint").Linter.Config} */
4 | export default config;
5 |
--------------------------------------------------------------------------------
/apps/www/app/community/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | export default async function CommunityPage() {
4 | redirect("https://discord.gg/fx99JWdfS7");
5 | }
6 |
--------------------------------------------------------------------------------
/apps/www/app/github/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | export default async function CommunityPage() {
4 | redirect("https://github.com/refrefhq/refref");
5 | }
6 |
--------------------------------------------------------------------------------
/packages/ui/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: { "@tailwindcss/postcss": {} },
4 | };
5 |
6 | export default config;
7 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/fraud-prevention.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Fraud Prevention
3 | description: Protecting your referral and affiliate programs from fraud and abuse with RefRef
4 | ---
5 | TBD
--------------------------------------------------------------------------------
/packages/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/typescript-config",
3 | "version": "0.0.0",
4 | "private": true,
5 | "publishConfig": {
6 | "access": "public"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/widget/sample.env:
--------------------------------------------------------------------------------
1 | # RefRef Widget Environment Variables
2 | # Copy this file to .env and update with your values
3 |
4 | # API Endpoint
5 | VITE_REFREF_API_ENDPOINT=http://localhost:3001
6 |
--------------------------------------------------------------------------------
/apps/www/lib/cn.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/typescript-config/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "jsx": "react-jsx"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/www/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/packages/widget/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/apps/acme/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/apps/webapp/src/server/db.ts:
--------------------------------------------------------------------------------
1 | import { createDb } from "@refref/coredb";
2 | import { env } from "@/env";
3 |
4 | export const db = createDb(env.DATABASE_URL);
5 | export { schema } from "@refref/coredb";
6 | export type { DBType } from "@refref/coredb";
7 |
--------------------------------------------------------------------------------
/packages/auth/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules", "dist"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "include": ["src"],
8 | "exclude": ["node_modules", "dist"]
9 | }
--------------------------------------------------------------------------------
/packages/types/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "tsup";
2 |
3 | export default defineConfig({
4 | entry: ["src/index.ts"],
5 | format: ["cjs", "esm"],
6 | dts: true,
7 | splitting: false,
8 | sourcemap: true,
9 | clean: true,
10 | });
11 |
--------------------------------------------------------------------------------
/packages/id/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/utils/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "rootDir": "./src"
6 | },
7 | "include": ["src/**/*"],
8 | "exclude": ["node_modules", "dist", "**/*.test.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/apps/www/app/api/search/route.ts:
--------------------------------------------------------------------------------
1 | import { source } from "@/lib/source";
2 | import { createFromSource } from "fumadocs-core/search/server";
3 |
4 | // it should be cached forever
5 | export const revalidate = false;
6 |
7 | export const { staticGET: GET } = createFromSource(source);
8 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/projects.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Projects
3 | description: Understanding projects in RefRef and how to manage them
4 | ---
5 | Projects are the core organizational unit in RefRef that allow you to create and manage distinct referral or affiliate programs.
6 |
7 |
--------------------------------------------------------------------------------
/packages/email-templates/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "jsx": "react",
5 | "outDir": "dist",
6 | "rootDir": "src"
7 | },
8 | "include": ["src"],
9 | "exclude": ["node_modules", "dist"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/utils/src/index.ts:
--------------------------------------------------------------------------------
1 | // Export plugins
2 | export { default as coredbPlugin } from "./plugins/coredb.js";
3 |
4 | // Export refcode utilities
5 | export {
6 | generateGlobalCode,
7 | validateVanityCode,
8 | normalizeCode,
9 | isGlobalCodeFormat,
10 | } from "./refcode.js";
11 |
--------------------------------------------------------------------------------
/packages/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@refref/ui/*": ["./src/*"]
7 | }
8 | },
9 | "include": ["."],
10 | "exclude": ["node_modules", "dist"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/widget/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import "./index.css";
4 | import App from "@/App";
5 |
6 | createRoot(document.getElementById("root")!).render(
7 |
8 |
9 | ,
10 | );
11 |
--------------------------------------------------------------------------------
/apps/api/.env.example:
--------------------------------------------------------------------------------
1 | # Server Configuration
2 | PORT=3001
3 | HOST=0.0.0.0
4 | NODE_ENV=development
5 | LOG_LEVEL=info
6 |
7 | # Database Configuration
8 | DATABASE_URL="postgresql://postgres:postgres@localhost:5432/refref"
9 |
10 | # Referral Configuration
11 | REFERRAL_HOST_URL="http://localhost:3002"
12 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/newsletter.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Newsletter Referral Programs
3 | description: Growing your newsletter audience through referral programs with RefRef
4 | ---
5 | This guide provides strategies for implementing successful referral programs to grow your newsletter audience using RefRef.
6 |
7 | TBD
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/programs/[id]/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function ProgramLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode;
5 | }) {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/api/auth/[...all]/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@/lib/auth";
2 | import { toNextJsHandler } from "better-auth/next-js";
3 |
4 | /**
5 | * BetterAuth handler for Next.js API routes
6 | * This route handles all authentication-related requests
7 | */
8 | export const { GET, POST } = toNextJsHandler(auth);
9 |
--------------------------------------------------------------------------------
/apps/webapp/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import path from "path";
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | environment: "node",
8 | },
9 | resolve: {
10 | alias: {
11 | "@": path.resolve(__dirname, "./src"),
12 | },
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/contact/page.tsx:
--------------------------------------------------------------------------------
1 | import Contact from "@/components/ui/contact";
2 |
3 | export const metadata = {
4 | title: "Contact",
5 | description:
6 | "Get in touch with the authors of RefRef for any questions or support.",
7 | };
8 |
9 | export default function ContactPage() {
10 | return ;
11 | }
12 |
--------------------------------------------------------------------------------
/apps/www/.env.example:
--------------------------------------------------------------------------------
1 | # S3 Configuration for fetch-content script
2 | AWS_ACCESS_KEY_ID=your_access_key
3 | AWS_SECRET_ACCESS_KEY=your_secret_key
4 | AWS_REGION=us-east-1
5 |
6 | # Optional: Override default S3 settings
7 | # S3_BUCKET_NAME=refref-www
8 | # S3_ENDPOINT=https://s3.wasabisys.com
9 | # BLOG_PREFIX=blog/
10 | # BLOG_IMAGES_PREFIX=blog-images/
--------------------------------------------------------------------------------
/packages/typescript-config/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "./base.json",
4 | "compilerOptions": {
5 | "plugins": [{ "name": "next" }],
6 | "module": "ESNext",
7 | "moduleResolution": "Bundler",
8 | "allowJs": true,
9 | "jsx": "preserve",
10 | "noEmit": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/apps/www/next.config.mjs:
--------------------------------------------------------------------------------
1 | import { createMDX } from 'fumadocs-mdx/next';
2 |
3 | const withMDX = createMDX();
4 |
5 | /** @type {import('next').NextConfig} */
6 | const config = {
7 | output: 'export',
8 | reactStrictMode: true,
9 | images: {
10 | domains: ['refref.ai'],
11 | unoptimized: true
12 | }
13 | };
14 |
15 | export default withMDX(config);
16 |
--------------------------------------------------------------------------------
/packages/ui/src/components/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
4 |
5 | function AspectRatio({
6 | ...props
7 | }: React.ComponentProps) {
8 | return ;
9 | }
10 |
11 | export { AspectRatio };
12 |
--------------------------------------------------------------------------------
/apps/assets/.gitignore:
--------------------------------------------------------------------------------
1 | # Build outputs
2 | dist/
3 |
4 | # Generated assets (built from packages)
5 | public/*.js
6 | public/*.js.map
7 |
8 | # Keep configuration files
9 | !public/_headers
10 | !public/_redirects
11 |
12 | # Dependencies
13 | node_modules/
14 |
15 | # TypeScript
16 | *.tsbuildinfo
17 |
18 | # Environment
19 | .env
20 | .env.local
21 | .env.*.local
22 |
--------------------------------------------------------------------------------
/packages/ui/src/components/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@refref/ui/lib/utils";
2 |
3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4 | return (
5 |
10 | );
11 | }
12 |
13 | export { Skeleton };
14 |
--------------------------------------------------------------------------------
/apps/assets/wrangler.toml:
--------------------------------------------------------------------------------
1 | # Wrangler configuration for RefRef Assets
2 | # https://developers.cloudflare.com/workers/wrangler/configuration/
3 |
4 | name = "refref-assets"
5 | main = "src/index.ts"
6 | compatibility_date = "2024-01-01"
7 |
8 | [assets]
9 | directory = "./public"
10 | binding = "ASSETS"
11 |
12 | # Dev/Preview environment
13 | [env.dev]
14 | name = "refref-assets-dev"
15 |
--------------------------------------------------------------------------------
/packages/attribution-script/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | RefRef Attribution Script Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/packages/attribution-script/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import path from "path";
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | environment: "jsdom",
8 | testTimeout: 30000,
9 | hookTimeout: 30000,
10 | },
11 | resolve: {
12 | alias: {
13 | "@": path.resolve(__dirname, "./src"),
14 | },
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import path from "path";
3 |
4 | export default defineConfig({
5 | test: {
6 | globals: true,
7 | environment: "node",
8 | testTimeout: 30000,
9 | hookTimeout: 30000,
10 | },
11 | resolve: {
12 | alias: {
13 | "@": path.resolve(__dirname, "./src"),
14 | },
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/apps/webapp/src/server/api/routers/post.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";
4 |
5 | export const postRouter = createTRPCRouter({
6 | hello: publicProcedure
7 | .input(z.object({ text: z.string() }))
8 | .query(({ input }) => {
9 | return {
10 | greeting: `Hello ${input.text}`,
11 | };
12 | }),
13 | });
14 |
--------------------------------------------------------------------------------
/packages/coredb/drizzle.config.ts:
--------------------------------------------------------------------------------
1 | import { type Config } from "drizzle-kit";
2 |
3 | if (!process.env.DATABASE_URL) {
4 | throw new Error(
5 | "DATABASE_URL environment variable is required for migrations",
6 | );
7 | }
8 |
9 | export default {
10 | schema: "./src/schema.ts",
11 | dialect: "postgresql",
12 | dbCredentials: {
13 | url: process.env.DATABASE_URL,
14 | },
15 | } satisfies Config;
16 |
--------------------------------------------------------------------------------
/apps/assets/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "outDir": "./dist",
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "target": "ES2022",
8 | "lib": ["ES2022"],
9 | "types": ["node", "@cloudflare/workers-types"]
10 | },
11 | "include": ["src/**/*", "scripts/**/*"],
12 | "exclude": ["node_modules", "dist", "public"]
13 | }
14 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/changelog/page.tsx:
--------------------------------------------------------------------------------
1 | import Changelog from "@/components/ui/changelog";
2 | import { SubscriptionForm } from "@/components/ui/subscription-form";
3 |
4 | export const metadata = {
5 | title: "Changelog",
6 | description: "Get the latest product updates and changes to RefRef.",
7 | };
8 |
9 | export default function ChangelogPage() {
10 | return (
11 | <>
12 |
13 | >
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/packages/widget/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React + TS
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/widget/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | }
15 |
--------------------------------------------------------------------------------
/packages/ui/src/components/spinner.tsx:
--------------------------------------------------------------------------------
1 | import { Loader2Icon } from "lucide-react";
2 |
3 | import { cn } from "@refref/ui/lib/utils";
4 |
5 | function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
6 | return (
7 |
13 | );
14 | }
15 |
16 | export { Spinner };
17 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/logger.ts:
--------------------------------------------------------------------------------
1 | import winston from "winston";
2 | const { combine, timestamp, colorize, simple, errors } = winston.format;
3 |
4 | const logger = winston.createLogger({
5 | level: "info",
6 | format: combine(
7 | errors({ stack: true }),
8 | timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
9 | colorize(),
10 | simple(),
11 | ),
12 | transports: [new winston.transports.Console()],
13 | });
14 |
15 | export { logger };
16 |
--------------------------------------------------------------------------------
/apps/www/scripts/generate-docs.mjs:
--------------------------------------------------------------------------------
1 | import * as OpenAPI from 'fumadocs-openapi';
2 | import { rimrafSync } from 'rimraf';
3 |
4 | const openapiOut = './content/docs/api';
5 |
6 | // clean generated files
7 | rimrafSync(openapiOut, {
8 | filter(v) {
9 | return !v.endsWith('index.mdx') && !v.endsWith('meta.json');
10 | },
11 | });
12 |
13 | void OpenAPI.generateFiles({
14 | // input files
15 | input: ['./openapi.yaml'],
16 | output: openapiOut,
17 | groupBy: 'tag',
18 | });
--------------------------------------------------------------------------------
/packages/coredb/drizzle/meta/_journal.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "7",
3 | "dialect": "postgresql",
4 | "entries": [
5 | {
6 | "idx": 0,
7 | "version": "7",
8 | "when": 1763471717017,
9 | "tag": "0000_flashy_infant_terrible",
10 | "breakpoints": true
11 | },
12 | {
13 | "idx": 1,
14 | "version": "7",
15 | "when": 1763895779098,
16 | "tag": "0001_flaky_madelyne_pryor",
17 | "breakpoints": true
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/packages/ui/src/lib/format.ts:
--------------------------------------------------------------------------------
1 | export function formatDate(
2 | date: Date | string | number | undefined,
3 | opts: Intl.DateTimeFormatOptions = {},
4 | ) {
5 | if (!date) return "";
6 |
7 | try {
8 | return new Intl.DateTimeFormat("en-US", {
9 | month: opts.month ?? "long",
10 | day: opts.day ?? "numeric",
11 | year: opts.year ?? "numeric",
12 | ...opts,
13 | }).format(new Date(date));
14 | } catch (_err) {
15 | return "";
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/email-templates/src/utils/render.ts:
--------------------------------------------------------------------------------
1 | import { render } from "@react-email/render";
2 | import * as React from "react";
3 |
4 | /**
5 | * Renders a React Email component to an HTML string
6 | * @param component - The React Email component to render
7 | * @returns The rendered HTML string
8 | */
9 | export async function renderEmail(
10 | component: React.ReactElement,
11 | ): Promise {
12 | return await render(component, {
13 | pretty: false,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/hooks/use-debounce.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useDebounce(value: T, delay: number): T {
4 | const [debouncedValue, setDebouncedValue] = useState(value);
5 |
6 | useEffect(() => {
7 | const handler = setTimeout(() => {
8 | setDebouncedValue(value);
9 | }, delay);
10 |
11 | return () => {
12 | clearTimeout(handler);
13 | };
14 | }, [value, delay]);
15 |
16 | return debouncedValue;
17 | }
18 |
--------------------------------------------------------------------------------
/apps/www/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/nextjs.json",
3 | "compilerOptions": {
4 | "paths": {
5 | "@/.source": ["./.source"],
6 | "@/*": ["./*"]
7 | },
8 | "declaration": false
9 | },
10 | "include": [
11 | "next-env.d.ts",
12 | "**/*.ts",
13 | "**/*.tsx",
14 | ".next/types/**/*.ts",
15 | "src/lib/dom/build/rrwebRecorderScript.cjs",
16 | "src/env.ts"
17 | , "scripts/lint.mts" ],
18 | "exclude": ["node_modules"]
19 | }
20 |
--------------------------------------------------------------------------------
/apps/api/src/routes/v1/track.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance } from "fastify";
2 | import signupTrackRoutes from "./track/signup.js";
3 | import purchaseTrackRoutes from "./track/purchase.js";
4 |
5 | export default async function trackRoutes(fastify: FastifyInstance) {
6 | // Register signup tracking route
7 | await fastify.register(signupTrackRoutes, { prefix: "/signup" });
8 |
9 | // Register purchase tracking route
10 | await fastify.register(purchaseTrackRoutes, { prefix: "/purchase" });
11 | }
12 |
--------------------------------------------------------------------------------
/packages/attribution-script/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["./src/*"]
7 | },
8 | "declaration": false,
9 | "declarationMap": false,
10 | "module": "ESNext",
11 | "moduleResolution": "Bundler",
12 | "allowJs": true,
13 | "jsx": "preserve",
14 | "noEmit": true
15 | },
16 | "include": ["**/*.ts", "**/*.tsx"],
17 | "exclude": ["node_modules", "dist"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/attribution-script/src/constants.ts:
--------------------------------------------------------------------------------
1 | import type { AutoAttachMode } from "@/types";
2 |
3 | export const DEFAULT_AUTO_ATTACH: AutoAttachMode = "data-refref";
4 |
5 | export const FORM = {
6 | SELECTOR: "form[data-refref]",
7 | SELECTOR_ALL: "form",
8 | FIELD: "refcode",
9 | } as const;
10 |
11 | export const URL = {
12 | CODE_PARAM: "refcode",
13 | } as const;
14 |
15 | export const COOKIE = {
16 | CODE_KEY: "refref-refcode",
17 | MAX_AGE: 90 * 24 * 60 * 60, // 90 days in seconds
18 | } as const;
19 |
--------------------------------------------------------------------------------
/apps/www/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "app/global.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/apps/api/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globals: true,
6 | environment: "node",
7 | testTimeout: 30000,
8 | hookTimeout: 30000,
9 | setupFiles: ["./test/setup.ts"],
10 | env: {
11 | NODE_ENV: "test",
12 | // Mock database is used via Vitest mocks, but we need a dummy URL
13 | // to satisfy the plugin's validation
14 | DATABASE_URL: "postgresql://mock:mock@localhost:5432/mock",
15 | },
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/apps/refer/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | globals: true,
6 | environment: "node",
7 | testTimeout: 30000,
8 | hookTimeout: 30000,
9 | setupFiles: ["./test/setup.ts"],
10 | env: {
11 | NODE_ENV: "test",
12 | // Mock database is used via Vitest mocks, but we need a dummy URL
13 | // to satisfy the plugin's validation
14 | DATABASE_URL: "postgresql://mock:mock@localhost:5432/mock",
15 | },
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/apps/www/.gitignore:
--------------------------------------------------------------------------------
1 | # deps
2 | /node_modules
3 |
4 | # generated content
5 | .contentlayer
6 | .content-collections
7 | .source
8 |
9 | # test & build
10 | /coverage
11 | /.next/
12 | /out/
13 | /build
14 | *.tsbuildinfo
15 |
16 | # misc
17 | .DS_Store
18 | *.pem
19 | /.pnp
20 | .pnp.js
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | # others
26 | .env*.local
27 | .vercel
28 | next-env.d.ts
29 |
30 | # sitemap
31 | /public/sitemap.xml
32 |
33 | # remote blog content
34 | /content/blogs/*
35 | /public/blog/*
36 | /content/tmp/*
--------------------------------------------------------------------------------
/packages/widget/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/react-library.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@/*": ["./src/*"]
7 | },
8 | "declaration": false,
9 | "declarationMap": false,
10 | "module": "ESNext",
11 | "moduleResolution": "Bundler",
12 | "allowJs": true,
13 | "jsx": "preserve",
14 | "noEmit": true
15 | },
16 | "include": ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts"],
17 | "exclude": ["node_modules", "dist"]
18 | }
19 |
--------------------------------------------------------------------------------
/apps/webapp/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "../../packages/ui/src/styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true
11 | },
12 | "iconLibrary": "lucide",
13 | "aliases": {
14 | "components": "@/components",
15 | "hooks": "@/hooks",
16 | "lib": "@/lib",
17 | "utils": "@refref/ui/lib/utils",
18 | "ui": "@refref/ui/components"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from "@/lib/auth";
2 | import { headers } from "next/headers";
3 | import { redirect } from "next/navigation";
4 |
5 | export default async function MainLayout({
6 | children,
7 | }: {
8 | children: React.ReactNode;
9 | }) {
10 | const session = await auth.api.getSession({
11 | headers: await headers(),
12 | });
13 |
14 | // Server-side redirect if no session is found
15 | if (!session) {
16 | redirect("/auth/sign-in");
17 | }
18 |
19 | return <>{children}>;
20 | }
21 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/build.config.ts:
--------------------------------------------------------------------------------
1 | import { defineBuildConfig } from "unbuild";
2 |
3 | export default defineBuildConfig({
4 | entries: [
5 | {
6 | input: "./src/index",
7 | outDir: "./dist",
8 | name: "index",
9 | format: ["cjs", "esm"],
10 | },
11 | ],
12 | declaration: true,
13 | clean: true,
14 | failOnWarn: false, // Don't fail on warnings
15 | rollup: {
16 | emitCJS: true,
17 | esbuild: {
18 | target: "node16",
19 | },
20 | },
21 | externals: ["better-auth", "zod"],
22 | });
23 |
--------------------------------------------------------------------------------
/packages/widget/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "../../packages/ui/src/styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true
11 | },
12 | "iconLibrary": "lucide",
13 | "aliases": {
14 | "components": "@/components",
15 | "hooks": "@/hooks",
16 | "lib": "@/lib",
17 | "utils": "@refref/ui/lib/utils",
18 | "ui": "@refref/ui/components"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true
11 | },
12 | "iconLibrary": "lucide",
13 | "aliases": {
14 | "components": "@refref/ui/components",
15 | "utils": "@refref/ui/lib/utils",
16 | "hooks": "@refref/ui/hooks",
17 | "lib": "@refref/ui/lib",
18 | "ui": "@refref/ui/components"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/apps/www/content/docs/api/get-programs.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Get all programs
3 | description: Retrieve a list of all programs
4 | full: true
5 | _openapi:
6 | method: GET
7 | route: /programs
8 | toc: []
9 | structuredData:
10 | headings: []
11 | contents:
12 | - content: Retrieve a list of all programs
13 | ---
14 |
15 | {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
16 |
17 |
--------------------------------------------------------------------------------
/packages/typescript-config/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "declaration": true,
5 | "declarationMap": true,
6 | "esModuleInterop": true,
7 | "incremental": false,
8 | "isolatedModules": true,
9 | "lib": ["es2022", "DOM", "DOM.Iterable"],
10 | "module": "NodeNext",
11 | "moduleDetection": "force",
12 | "moduleResolution": "NodeNext",
13 | "noUncheckedIndexedAccess": true,
14 | "resolveJsonModule": true,
15 | "skipLibCheck": true,
16 | "strict": true,
17 | "target": "ES2022"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/coredb/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/base.json",
3 | "compilerOptions": {
4 | "module": "ESNext",
5 | "target": "ES2022",
6 | "lib": ["ES2022"],
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "declaration": true,
10 | "declarationMap": true,
11 | "moduleResolution": "bundler",
12 | "allowSyntheticDefaultImports": true,
13 | "esModuleInterop": true,
14 | "skipLibCheck": true,
15 | "strict": true,
16 | "noUncheckedIndexedAccess": true
17 | },
18 | "include": ["src/**/*"],
19 | "exclude": ["node_modules", "dist"]
20 | }
--------------------------------------------------------------------------------
/apps/www/content/docs/api/get-participants.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Get all participants
3 | description: Retrieve a list of all participants
4 | full: true
5 | _openapi:
6 | method: GET
7 | route: /participants
8 | toc: []
9 | structuredData:
10 | headings: []
11 | contents:
12 | - content: Retrieve a list of all participants
13 | ---
14 |
15 | {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
16 |
17 |
--------------------------------------------------------------------------------
/apps/e2e/sanity/pages/acme/acme-base-page.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 | import { BasePage } from "../base-page";
3 |
4 | /**
5 | * Base page for Acme test application pages
6 | * Acme app runs on port 3003
7 | */
8 | export class AcmeBasePage extends BasePage {
9 | protected baseUrl: string;
10 |
11 | constructor(page: Page) {
12 | super(page);
13 | this.baseUrl = process.env.ACME_URL || "http://localhost:3003";
14 | }
15 |
16 | /**
17 | * Navigate to a specific path in the Acme app
18 | */
19 | async goto(path: string) {
20 | await this.page.goto(`${this.baseUrl}${path}`);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { ReactNode } from "react";
2 | import { HomeLayout } from "fumadocs-ui/layouts/home";
3 | import { baseOptions } from "@/app/layout.config";
4 | import { Footer } from "@/components/ui/footer";
5 |
6 | export default function Layout({
7 | children,
8 | }: {
9 | children: ReactNode;
10 | }): React.ReactElement {
11 | return (
12 |
13 |
14 |
15 | {children}
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/apps/www/content/docs/api/get-program-by-id.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Get program by ID
3 | description: Retrieve a specific program by its ID
4 | full: true
5 | _openapi:
6 | method: GET
7 | route: /programs/{programId}
8 | toc: []
9 | structuredData:
10 | headings: []
11 | contents:
12 | - content: Retrieve a specific program by its ID
13 | ---
14 |
15 | {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
16 |
17 |
--------------------------------------------------------------------------------
/apps/acme/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/acme",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "Test application for RefRef integration testing and demos",
6 | "scripts": {
7 | "dev": "next dev --port 3003",
8 | "build": "next build",
9 | "start": "next start --port 3003"
10 | },
11 | "dependencies": {
12 | "jose": "^6.1.1",
13 | "next": "15.1.9",
14 | "react": "^19.2.0",
15 | "react-dom": "^19.2.0"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^20.11.5",
19 | "@types/react": "^19.2.0",
20 | "@types/react-dom": "^19.2.0",
21 | "typescript": "^5.3.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/apps/api/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "lib": ["ES2022"],
6 | "moduleResolution": "bundler",
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": true
19 | },
20 | "include": ["src/**/*"],
21 | "exclude": ["node_modules", "dist"]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/refer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "lib": ["ES2022"],
6 | "moduleResolution": "bundler",
7 | "outDir": "./dist",
8 | "rootDir": "./src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "skipLibCheck": true,
12 | "forceConsistentCasingInFileNames": true,
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noUnusedLocals": true,
16 | "noUnusedParameters": true,
17 | "noImplicitReturns": true,
18 | "noFallthroughCasesInSwitch": true
19 | },
20 | "include": ["src/**/*"],
21 | "exclude": ["node_modules", "dist"]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/webapp/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@refref/typescript-config/nextjs.json",
3 | "compilerOptions": {
4 | "types": ["vitest/globals"],
5 | "paths": {
6 | "@/*": [
7 | "./src/*"
8 | ],
9 | "@refref/ui/*": [
10 | "../../packages/ui/src/*"
11 | ]
12 | },
13 | "disableSizeLimit": false,
14 | "declaration": false,
15 | "declarationMap": false
16 | },
17 | "include": [
18 | "**/*.ts",
19 | "**/*.tsx",
20 | "next-env.d.ts",
21 | "next.config.ts",
22 | "src/types/**/*.ts",
23 | ".next/types/**/*.ts"
24 | ],
25 | "exclude": [
26 | "node_modules",
27 | ".next"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/apps/webapp/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = useState(undefined);
7 |
8 | useEffect(() => {
9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
10 | const onChange = () => {
11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
12 | };
13 | mql.addEventListener("change", onChange);
14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
15 | return () => mql.removeEventListener("change", onChange);
16 | }, []);
17 |
18 | return !!isMobile;
19 | }
20 |
--------------------------------------------------------------------------------
/apps/www/content/docs/api/get-participant-by-id.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Get participant by ID
3 | description: Retrieve a specific participant by their ID
4 | full: true
5 | _openapi:
6 | method: GET
7 | route: /participants/{participantId}
8 | toc: []
9 | structuredData:
10 | headings: []
11 | contents:
12 | - content: Retrieve a specific participant by their ID
13 | ---
14 |
15 | {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
16 |
17 |
--------------------------------------------------------------------------------
/apps/refer/.env.example:
--------------------------------------------------------------------------------
1 | # RefRef Refer Server Environment Variables
2 | # ==========================================
3 | # Copy this file to .env and fill in your values
4 |
5 | # Database Configuration (REQUIRED)
6 | # PostgreSQL connection string
7 | # For Docker: use the default below
8 | # For local: update with your PostgreSQL credentials
9 | DATABASE_URL="postgresql://postgres:postgres@localhost:5432/refref"
10 |
11 | # Server Configuration (OPTIONAL)
12 | # Port for the refer server (default: 3002)
13 | PORT=3002
14 |
15 | # Host to bind to (default: 0.0.0.0)
16 | HOST="0.0.0.0"
17 |
18 | # Log level: trace, debug, info, warn, error, fatal (default: info)
19 | LOG_LEVEL="info"
20 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @refref/better-auth
3 | *
4 | * Better Auth integration for RefRef referral tracking.
5 | * Automatically tracks user signups with referral attribution.
6 | */
7 |
8 | // Main plugin export
9 | export { refrefAnalytics } from "./plugin";
10 |
11 | // Type exports
12 | export type {
13 | RefRefPluginOptions,
14 | SignupTrackingData,
15 | TrackingResponse,
16 | SignupTrackingPayload,
17 | } from "./types";
18 |
19 | // Utility exports (for advanced usage)
20 | export { RefRefAPIClient } from "./utils/api-client";
21 | export {
22 | extractRefcodeFromRequest,
23 | extractRefcodeFromContext,
24 | } from "./utils/cookie";
25 |
--------------------------------------------------------------------------------
/packages/attribution-script/src/types.ts:
--------------------------------------------------------------------------------
1 | export type AutoAttachMode = "false" | "data-refref" | "all";
2 |
3 | /**
4 | * MDN-compliant cookie attributes
5 | * Accepts any cookie attribute as per MDN spec.
6 | * Only attributes with defaults are explicitly typed.
7 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
8 | */
9 | export interface CookieOptions extends Record {
10 | Path?: string; // Default: "/"
11 | "Max-Age"?: number; // Default: 7776000 (90 days in seconds)
12 | SameSite?: "Strict" | "Lax" | "None"; // Default: "Lax"
13 | }
14 |
15 | export interface FormElement extends HTMLFormElement {
16 | [key: string]: unknown;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | const MOBILE_BREAKPOINT = 768;
4 |
5 | export function useIsMobile() {
6 | const [isMobile, setIsMobile] = React.useState(
7 | undefined,
8 | );
9 |
10 | React.useEffect(() => {
11 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12 | const onChange = () => {
13 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14 | };
15 | mql.addEventListener("change", onChange);
16 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17 | return () => mql.removeEventListener("change", onChange);
18 | }, []);
19 |
20 | return !!isMobile;
21 | }
22 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/rewards/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SiteHeader } from "@/components/site-header";
4 | import { SiteBreadcrumbs } from "@/components/site-breadcrumbs";
5 | import { RewardsTable } from "@/components/rewards-table/rewards-table";
6 |
7 | export default function RewardsPage() {
8 | const breadcrumbs = [{ label: "Rewards", href: "/rewards" }];
9 |
10 | return (
11 | <>
12 | } />
13 |
18 | >
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/apps/www/source.config.ts:
--------------------------------------------------------------------------------
1 | import {
2 | defineConfig,
3 | defineDocs,
4 | defineCollections,
5 | frontmatterSchema,
6 | } from "fumadocs-mdx/config";
7 | import { z } from "zod";
8 |
9 | export const { docs, meta } = defineDocs({
10 | dir: "content/docs",
11 | });
12 |
13 | export const blog = defineCollections({
14 | type: "doc",
15 | dir: "content/blogs",
16 | async: true,
17 | schema: frontmatterSchema.extend({
18 | author: z.string(),
19 | image: z.string(),
20 | date: z.string().date().or(z.date()).optional(),
21 | priority: z.number().default(0),
22 | }),
23 | });
24 |
25 | export default defineConfig({
26 | mdxOptions: {
27 | // MDX options
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/apps/e2e/scripts/cleanup-ports.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Cleanup script to kill processes on test server ports
4 | # This ensures a clean state before running Playwright tests
5 |
6 | PORTS=(3000 3001 3002 3003 8787)
7 |
8 | echo "Cleaning up processes on test server ports..."
9 |
10 | for PORT in "${PORTS[@]}"; do
11 | echo -n " Port $PORT: "
12 |
13 | # Find PIDs using the port
14 | PIDS=$(lsof -ti :$PORT 2>/dev/null)
15 |
16 | if [ -z "$PIDS" ]; then
17 | echo "✓ Free"
18 | else
19 | # Kill the processes
20 | echo "$PIDS" | xargs kill -9 2>/dev/null
21 | echo "✓ Cleaned (killed PIDs: $PIDS)"
22 | fi
23 | done
24 |
25 | echo ""
26 | echo "All ports cleaned. Ready to run tests."
27 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/activity/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SiteHeader } from "@/components/site-header";
4 | import { SiteBreadcrumbs } from "@/components/site-breadcrumbs";
5 | import { ActivityTable } from "@/components/activity-table/activity-table";
6 |
7 | export default function ActivityPage() {
8 | const breadcrumbs = [{ label: "Activity", href: "/activity" }];
9 |
10 | return (
11 | <>
12 | } />
13 |
18 | >
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/apps/www/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as LabelPrimitive from "@radix-ui/react-label";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | function Label({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
21 | );
22 | }
23 |
24 | export { Label };
25 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/participants/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { SiteHeader } from "@/components/site-header";
4 | import { SiteBreadcrumbs } from "@/components/site-breadcrumbs";
5 | import { ParticipantsTable } from "@/components/participants-table/participants-table";
6 |
7 | export default function ParticipantsPage() {
8 | const breadcrumbs = [{ label: "Participants", href: "/participants" }];
9 |
10 | return (
11 | <>
12 | } />
13 |
18 | >
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/packages/ui/src/components/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as LabelPrimitive from "@radix-ui/react-label";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function Label({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
21 | );
22 | }
23 |
24 | export { Label };
25 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/eslint-config",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "exports": {
7 | "./base": "./base.js",
8 | "./next-js": "./next.js",
9 | "./react-internal": "./react-internal.js"
10 | },
11 | "devDependencies": {
12 | "@eslint/js": "^9.20.0",
13 | "@next/eslint-plugin-next": "^15.1.6",
14 | "eslint": "^9.20.0",
15 | "eslint-config-prettier": "^10.0.1",
16 | "eslint-plugin-only-warn": "^1.1.0",
17 | "eslint-plugin-react": "^7.37.4",
18 | "eslint-plugin-react-hooks": "^5.1.0",
19 | "eslint-plugin-turbo": "^2.4.0",
20 | "globals": "^15.15.0",
21 | "typescript": "^5.9.2",
22 | "typescript-eslint": "^8.39.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/webapp/instrumentation-client.ts:
--------------------------------------------------------------------------------
1 | import posthog from "posthog-js";
2 | import { env } from "./src/env";
3 |
4 | // Only initialize PostHog if key is present and enabled
5 | if (env.NEXT_PUBLIC_POSTHOG_KEY) {
6 | if (env.NEXT_PUBLIC_POSTHOG_ENABLED) {
7 | posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
8 | api_host: "/ingest",
9 | ui_host: "https://us.posthog.com",
10 | defaults: "2025-05-24",
11 | capture_exceptions: true, // This enables capturing exceptions using Error Tracking
12 | debug: process.env.NODE_ENV === "development",
13 | loaded: (ph) => {
14 | if (!env.NEXT_PUBLIC_POSTHOG_ENABLED) {
15 | ph.opt_out_capturing();
16 | }
17 | },
18 | });
19 | }
20 | }
21 |
22 | export { posthog };
23 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/organization/general/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { OrganizationSettingsCards } from "@daveyplate/better-auth-ui";
4 | import { Separator } from "@refref/ui/components/separator";
5 |
6 | export default function OrganizationGeneralSettings() {
7 | return (
8 |
9 |
10 |
Organization
11 |
12 | Manage your organization information and settings
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/apps/api/src/routes/openapi.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
2 | import { readFileSync } from "node:fs";
3 | import { join, dirname } from "node:path";
4 | import { fileURLToPath } from "node:url";
5 |
6 | const __filename = fileURLToPath(import.meta.url);
7 | const __dirname = dirname(__filename);
8 |
9 | // Read OpenAPI spec at module load time
10 | const openapiSpec = readFileSync(
11 | join(__dirname, "../../openapi.yaml"),
12 | "utf-8",
13 | );
14 |
15 | export default async function openapiRoutes(fastify: FastifyInstance) {
16 | fastify.get("/openapi", openapiHandler);
17 | }
18 |
19 | async function openapiHandler(_request: FastifyRequest, reply: FastifyReply) {
20 | return reply.type("text/yaml").send(openapiSpec);
21 | }
22 |
--------------------------------------------------------------------------------
/apps/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "ESNext",
5 | "lib": ["ES2022"],
6 | "moduleResolution": "bundler",
7 | "outDir": "./dist",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noUnusedLocals": true,
15 | "noUnusedParameters": true,
16 | "noImplicitReturns": true,
17 | "noFallthroughCasesInSwitch": true,
18 | "types": ["node", "@playwright/test"]
19 | },
20 | "include": [
21 | "sanity/**/*",
22 | "utils/**/*",
23 | "playwright.config.ts"
24 | ],
25 | "exclude": ["node_modules", "dist", "test-results"]
26 | }
27 |
--------------------------------------------------------------------------------
/apps/www/lib/source.ts:
--------------------------------------------------------------------------------
1 | import { docs, meta, blog as blogPosts } from "@/.source";
2 | import { createMDXSource } from "fumadocs-mdx";
3 | import { loader } from "fumadocs-core/source";
4 | import { attachFile, createOpenAPI } from "fumadocs-openapi/server";
5 | import { icons } from "lucide-react";
6 | import { createElement } from "react";
7 |
8 | export const source = loader({
9 | baseUrl: "/docs",
10 | icon(icon) {
11 | if (icon && icon in icons)
12 | return createElement(icons[icon as keyof typeof icons]);
13 | },
14 | source: createMDXSource(docs, meta),
15 | pageTree: {
16 | attachFile,
17 | },
18 | });
19 |
20 | export const blog = loader({
21 | baseUrl: "/blog",
22 | source: createMDXSource(blogPosts, meta),
23 | });
24 |
25 | export const openapi = createOpenAPI();
26 |
--------------------------------------------------------------------------------
/packages/eslint-config/base.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import turboPlugin from "eslint-plugin-turbo";
4 | import tseslint from "typescript-eslint";
5 | import onlyWarn from "eslint-plugin-only-warn";
6 |
7 | /**
8 | * A shared ESLint configuration for the repository.
9 | *
10 | * @type {import("eslint").Linter.Config}
11 | * */
12 | export const config = [
13 | js.configs.recommended,
14 | eslintConfigPrettier,
15 | ...tseslint.configs.recommended,
16 | {
17 | plugins: {
18 | turbo: turboPlugin,
19 | },
20 | rules: {
21 | "turbo/no-undeclared-env-vars": "warn",
22 | },
23 | },
24 | {
25 | plugins: {
26 | onlyWarn,
27 | },
28 | },
29 | {
30 | ignores: ["dist/**"],
31 | },
32 | ];
33 |
--------------------------------------------------------------------------------
/apps/webapp/src/trpc/query-client.ts:
--------------------------------------------------------------------------------
1 | import {
2 | defaultShouldDehydrateQuery,
3 | QueryClient,
4 | } from "@tanstack/react-query";
5 | import SuperJSON from "superjson";
6 |
7 | export const createQueryClient = () =>
8 | new QueryClient({
9 | defaultOptions: {
10 | queries: {
11 | // With SSR, we usually want to set some default staleTime
12 | // above 0 to avoid refetching immediately on the client
13 | staleTime: 30 * 1000,
14 | },
15 | dehydrate: {
16 | serializeData: SuperJSON.serialize,
17 | shouldDehydrateQuery: (query) =>
18 | defaultShouldDehydrateQuery(query) ||
19 | query.state.status === "pending",
20 | },
21 | hydrate: {
22 | deserializeData: SuperJSON.deserialize,
23 | },
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/apps/e2e/sanity/pages/base-page.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 |
3 | /**
4 | * Base page class that all page objects extend
5 | * Provides common functionality for page interactions
6 | */
7 | export class BasePage {
8 | constructor(protected page: Page) {}
9 |
10 | /**
11 | * Navigate to a specific path
12 | */
13 | async goto(path: string) {
14 | await this.page.goto(path);
15 | }
16 |
17 | /**
18 | * Wait for the page to be fully loaded
19 | */
20 | async waitForPageLoad() {
21 | await this.page.waitForLoadState("networkidle");
22 | }
23 |
24 | /**
25 | * Take a screenshot (useful for debugging)
26 | */
27 | async screenshot(name: string) {
28 | await this.page.screenshot({
29 | path: `test-results/screenshots/${name}.png`,
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/product/secrets/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Separator } from "@refref/ui/components/separator";
4 | import { ProductSecretsCard } from "@/components/settings/product/product-secrets-card";
5 |
6 | export default function ProductSecretsSettings() {
7 | return (
8 |
9 |
10 |
Secrets
11 |
12 | View your product credentials for API integration
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/apps/www/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6 | return (
7 |
15 | );
16 | }
17 |
18 | export { Textarea };
19 |
--------------------------------------------------------------------------------
/apps/api/src/index.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config";
2 | import { buildApp } from "./app.js";
3 |
4 | const start = async () => {
5 | try {
6 | const app = await buildApp();
7 |
8 | const port = Number(process.env.PORT) || 3001;
9 | const host = process.env.HOST || "0.0.0.0";
10 |
11 | await app.listen({ port, host });
12 |
13 | app.log.info(`Server listening on ${host}:${port}`);
14 |
15 | // Graceful shutdown handlers
16 | const signals = ["SIGINT", "SIGTERM"] as const;
17 | for (const signal of signals) {
18 | process.on(signal, async () => {
19 | app.log.info(`Received ${signal}, closing server gracefully...`);
20 | await app.close();
21 | process.exit(0);
22 | });
23 | }
24 | } catch (err) {
25 | console.error(err);
26 | process.exit(1);
27 | }
28 | };
29 |
30 | start();
31 |
--------------------------------------------------------------------------------
/apps/e2e/sanity/pages/acme/acme-signup-page.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 | import { AcmeBasePage } from "./acme-base-page";
3 |
4 | /**
5 | * Page object for Acme app signup page
6 | * TODO: Implement in Task 3
7 | */
8 | export class AcmeSignupPage extends AcmeBasePage {
9 | constructor(page: Page) {
10 | super(page);
11 | }
12 |
13 | /**
14 | * Navigate to signup page with referral link
15 | * TODO: Implement in Task 3
16 | */
17 | async gotoWithReferralLink(referralUrl: string) {
18 | // TODO: Implement in Task 3
19 | throw new Error("Not implemented");
20 | }
21 |
22 | /**
23 | * Sign up as a referred user
24 | * TODO: Implement in Task 3
25 | */
26 | async signup(name: string, email: string) {
27 | // TODO: Implement in Task 3
28 | throw new Error("Not implemented");
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/ui/src/lib/id.ts:
--------------------------------------------------------------------------------
1 | import { customAlphabet } from "nanoid";
2 |
3 | const prefixes: Record = {};
4 |
5 | interface GenerateIdOptions {
6 | length?: number;
7 | separator?: string;
8 | }
9 |
10 | export function generateId(
11 | prefixOrOptions?: keyof typeof prefixes | GenerateIdOptions,
12 | inputOptions: GenerateIdOptions = {},
13 | ) {
14 | const finalOptions =
15 | typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions;
16 |
17 | const prefix =
18 | typeof prefixOrOptions === "object" ? undefined : prefixOrOptions;
19 |
20 | const { length = 12, separator = "_" } = finalOptions;
21 | const id = customAlphabet(
22 | "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
23 | length,
24 | )();
25 |
26 | return prefix ? `${prefixes[prefix]}${separator}${id}` : id;
27 | }
28 |
--------------------------------------------------------------------------------
/apps/refer/src/index.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config";
2 | import { buildApp } from "./app.js";
3 |
4 | const start = async () => {
5 | try {
6 | const app = await buildApp();
7 |
8 | const port = Number(process.env.PORT) || 3002;
9 | const host = process.env.HOST || "0.0.0.0";
10 |
11 | await app.listen({ port, host });
12 |
13 | app.log.info(`Refer server listening on ${host}:${port}`);
14 |
15 | // Graceful shutdown handlers
16 | const signals = ["SIGINT", "SIGTERM"] as const;
17 | for (const signal of signals) {
18 | process.on(signal, async () => {
19 | app.log.info(`Received ${signal}, closing server gracefully...`);
20 | await app.close();
21 | process.exit(0);
22 | });
23 | }
24 | } catch (err) {
25 | console.error(err);
26 | process.exit(1);
27 | }
28 | };
29 |
30 | start();
31 |
--------------------------------------------------------------------------------
/apps/acme/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "noEmit": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "bundler",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "incremental": true,
19 | "plugins": [
20 | {
21 | "name": "next"
22 | }
23 | ],
24 | "paths": {
25 | "@/*": [
26 | "./src/*"
27 | ]
28 | },
29 | "target": "ES2017"
30 | },
31 | "include": [
32 | "next-env.d.ts",
33 | "**/*.ts",
34 | "**/*.tsx",
35 | ".next/types/**/*.ts"
36 | ],
37 | "exclude": [
38 | "node_modules"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/apps/api/test/utils/testServer.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance } from "fastify";
2 | import { buildApp } from "../../src/app.js";
3 |
4 | let testServer: FastifyInstance | null = null;
5 |
6 | export async function startTestServer(): Promise<{
7 | server: FastifyInstance;
8 | url: string;
9 | }> {
10 | if (testServer) {
11 | throw new Error("Test server is already running");
12 | }
13 |
14 | testServer = await buildApp();
15 |
16 | const port = Math.floor(Math.random() * 10000) + 30000; // Random port between 30000-40000
17 | await testServer.listen({ port, host: "127.0.0.1" });
18 |
19 | const url = `http://127.0.0.1:${port}`;
20 |
21 | return { server: testServer, url };
22 | }
23 |
24 | export async function stopTestServer(): Promise {
25 | if (testServer) {
26 | await testServer.close();
27 | testServer = null;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function Separator({
9 | className,
10 | orientation = "horizontal",
11 | decorative = true,
12 | ...props
13 | }: React.ComponentProps) {
14 | return (
15 |
25 | );
26 | }
27 |
28 | export { Separator };
29 |
--------------------------------------------------------------------------------
/packages/widget/src/lib/store.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from "zustand/vanilla";
2 | import { create } from "zustand";
3 | import { WidgetConfigType, WidgetStore } from "@refref/types";
4 | import { defaultConfig } from "./config";
5 |
6 | export const widgetStore = createStore((set) => ({
7 | initialized: false,
8 | token: undefined,
9 | productId: undefined,
10 | widgetElementSelector: null,
11 | isOpen: false,
12 | config: defaultConfig,
13 | participantId: "",
14 | referralLinks: {},
15 | setIsOpen: (isOpen) => set({ isOpen }),
16 | setToken: (token) => set({ token }),
17 | setConfig: (newConfig) =>
18 | set((state) => ({
19 | config: { ...state.config, ...newConfig },
20 | })),
21 | toggle: () => set((state) => ({ isOpen: !state.isOpen })),
22 | setReferralLinks: (links) => set({ referralLinks: links }),
23 | }));
24 |
--------------------------------------------------------------------------------
/apps/refer/test/utils/testServer.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance } from "fastify";
2 | import { buildApp } from "../../src/app.js";
3 |
4 | let testServer: FastifyInstance | null = null;
5 |
6 | export async function startTestServer(): Promise<{
7 | server: FastifyInstance;
8 | url: string;
9 | }> {
10 | if (testServer) {
11 | throw new Error("Test server is already running");
12 | }
13 |
14 | testServer = await buildApp();
15 |
16 | const port = Math.floor(Math.random() * 10000) + 30000; // Random port between 30000-40000
17 | await testServer.listen({ port, host: "127.0.0.1" });
18 |
19 | const url = `http://127.0.0.1:${port}`;
20 |
21 | return { server: testServer, url };
22 | }
23 |
24 | export async function stopTestServer(): Promise {
25 | if (testServer) {
26 | await testServer.close();
27 | testServer = null;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@refref/ui/lib/utils";
4 |
5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6 | return (
7 |
15 | );
16 | }
17 |
18 | export { Textarea };
19 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-callback-ref.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | /**
4 | * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
5 | */
6 |
7 | /**
8 | * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
9 | * prop or avoid re-executing effects when passed as a dependency
10 | */
11 | function useCallbackRef unknown>(
12 | callback: T | undefined,
13 | ): T {
14 | const callbackRef = React.useRef(callback);
15 |
16 | React.useEffect(() => {
17 | callbackRef.current = callback;
18 | });
19 |
20 | // https://github.com/facebook/react/issues/19240
21 | return React.useMemo(
22 | () => ((...args) => callbackRef.current?.(...args)) as T,
23 | [],
24 | );
25 | }
26 |
27 | export { useCallbackRef };
28 |
--------------------------------------------------------------------------------
/packages/ui/src/hooks/use-debounced-callback.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { useCallbackRef } from "@refref/ui/hooks/use-callback-ref";
4 |
5 | export function useDebouncedCallback unknown>(
6 | callback: T,
7 | delay: number,
8 | ) {
9 | const handleCallback = useCallbackRef(callback);
10 | const debounceTimerRef = React.useRef(0);
11 | React.useEffect(
12 | () => () => window.clearTimeout(debounceTimerRef.current),
13 | [],
14 | );
15 |
16 | const setValue = React.useCallback(
17 | (...args: Parameters) => {
18 | window.clearTimeout(debounceTimerRef.current);
19 | debounceTimerRef.current = window.setTimeout(
20 | () => handleCallback(...args),
21 | delay,
22 | );
23 | },
24 | [handleCallback, delay],
25 | );
26 |
27 | return setValue;
28 | }
29 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/auth/layout.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | import { auth } from "@/lib/auth";
3 | import { headers } from "next/headers";
4 | import { Toaster } from "sonner";
5 |
6 | /**
7 | * Auth layout that redirects authenticated users to the dashboard
8 | * and provides a consistent layout for all authentication pages.
9 | */
10 | export default async function AuthLayout({
11 | children,
12 | }: {
13 | children: React.ReactNode;
14 | }) {
15 | const session = await auth.api.getSession({
16 | headers: await headers(),
17 | });
18 |
19 | // Redirect to home if user is already authenticated (will redirect to product)
20 | if (session) {
21 | redirect("/");
22 | }
23 |
24 | return (
25 | <>
26 |
27 | {children}
28 | >
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/onboarding/layout.tsx:
--------------------------------------------------------------------------------
1 | import { auth } from "@/lib/auth";
2 | import { headers } from "next/headers";
3 | import { redirect } from "next/navigation";
4 | import { db, schema } from "@/server/db";
5 | import { eq } from "drizzle-orm";
6 |
7 | export default async function OnboardingLayout({
8 | children,
9 | }: {
10 | children: React.ReactNode;
11 | }) {
12 | const session = await auth.api.getSession({
13 | headers: await headers(),
14 | });
15 |
16 | // Check if user has any products in their active organization
17 | if (session?.session.activeOrganizationId) {
18 | const existingProduct = await db.query.product.findFirst({
19 | where: eq(schema.product.orgId, session.session.activeOrganizationId),
20 | });
21 |
22 | if (existingProduct) {
23 | redirect("/programs");
24 | }
25 | }
26 |
27 | return <>{children}>;
28 | }
29 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/organization/api-keys/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { OrganizationApiKeysCard } from "@/components/settings/organization/organization-api-keys-card";
4 | import { Separator } from "@refref/ui/components/separator";
5 |
6 | export default function OrganizationAPIKeysPage() {
7 | return (
8 |
9 | {/* Header Section */}
10 |
11 |
API Keys
12 |
13 | Manage API keys for accessing your organization's data
14 | programmatically
15 |
16 |
17 |
18 |
19 |
20 | {/* API Keys Card */}
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # Dependencies
4 | node_modules
5 | .pnp
6 | .pnp.js
7 |
8 | # Local env files
9 | .env
10 | .env.local
11 | .env.development.local
12 | .env.test.local
13 | .env.production.local
14 | version.py
15 | push-to-dockerhub.ps1
16 |
17 | # Testing
18 | coverage
19 |
20 | # Turbo
21 | .turbo
22 |
23 | # Vercel
24 | .vercel
25 |
26 | # Build Outputs
27 | next-env.d.ts
28 | .next/
29 | out/
30 | build
31 | dist
32 |
33 |
34 | # Debug
35 | npm-debug.log*
36 | yarn-debug.log*
37 | yarn-error.log*
38 |
39 | # Misc
40 | .DS_Store
41 | *.pem
42 | CLAUDE.md
43 | .serena
44 | .local
45 | .claude
46 | .infisical.json
47 | .wrangler
48 | fly.toml
49 | .playwright-mcp
50 |
51 | # Testing
52 | apps/e2e/playwright-report
53 | apps/e2e/test-results
54 | apps/e2e/.env.test
55 |
56 | # IDEs and editors
57 | .vscode/
58 |
--------------------------------------------------------------------------------
/apps/acme/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import Script from "next/script";
3 | import { refrefConfig } from "@/lib/refref-config";
4 |
5 | export const metadata: Metadata = {
6 | title: "ACME - Test Application",
7 | description: "Test application for RefRef integration testing and demos",
8 | };
9 |
10 | export default function RootLayout({
11 | children,
12 | }: {
13 | children: React.ReactNode;
14 | }) {
15 | return (
16 |
17 |
20 | {children}
21 |
22 | {/* RefRef Attribution Script */}
23 |
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/packages/types/src/event-config.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | // Event Definition Config V1
4 | export const eventDefinitionConfigV1Schema = z.object({
5 | schemaVersion: z.literal(1),
6 | requiredFields: z.array(z.string()).optional(),
7 | validationRules: z.record(z.string(), z.unknown()).optional(),
8 | });
9 | export type EventDefinitionConfigV1Type = z.infer<
10 | typeof eventDefinitionConfigV1Schema
11 | >;
12 |
13 | // Event Metadata V1
14 | export const eventMetadataV1Schema = z.object({
15 | schemaVersion: z.literal(1),
16 | source: z.enum(["auto", "api"]).optional(),
17 | reason: z.string().optional(),
18 | orderAmount: z.number().optional(),
19 | orderId: z.string().optional(),
20 | productIds: z.array(z.string()).optional(),
21 | customData: z.record(z.string(), z.unknown()).optional(),
22 | });
23 | export type EventMetadataV1Type = z.infer;
24 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/organization/members/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | OrganizationMembersCard,
5 | OrganizationInvitationsCard,
6 | } from "@daveyplate/better-auth-ui";
7 | import { Separator } from "@refref/ui/components/separator";
8 |
9 | export default function OrganizationMembersPage() {
10 | return (
11 |
12 |
13 |
Members
14 |
15 | Manage organization members and invitations
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/packages/types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/types",
3 | "version": "0.0.1",
4 | "private": true,
5 | "main": "./dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.mjs",
12 | "require": "./dist/index.js"
13 | }
14 | },
15 | "files": [
16 | "dist/**"
17 | ],
18 | "scripts": {
19 | "build": "tsup",
20 | "dev": "tsup --watch",
21 | "lint": "eslint .",
22 | "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
23 | "type:check": "tsc --noEmit"
24 | },
25 | "devDependencies": {
26 | "@refref/typescript-config": "workspace:*",
27 | "@refref/eslint-config": "workspace:*",
28 | "tsup": "^8.0.2",
29 | "typescript": "^5.9.2"
30 | },
31 | "dependencies": {
32 | "zod": "^4.0.17"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/account/profile/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | UpdateNameCard,
5 | UpdateAvatarCard,
6 | ChangeEmailCard,
7 | } from "@daveyplate/better-auth-ui";
8 | import { Separator } from "@refref/ui/components/separator";
9 |
10 | export default function AccountProfilePage() {
11 | return (
12 |
13 |
14 |
Profile
15 |
16 | Manage your personal account information
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/packages/ui/src/components/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ProgressPrimitive from "@radix-ui/react-progress";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function Progress({
9 | className,
10 | value,
11 | ...props
12 | }: React.ComponentProps) {
13 | return (
14 |
22 |
27 |
28 | );
29 | }
30 |
31 | export { Progress };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "refref",
3 | "private": true,
4 | "scripts": {
5 | "build": "turbo run build",
6 | "dev": "infisical run --env=dev -- turbo run dev || turbo run dev",
7 | "lint": "turbo run lint",
8 | "format": "prettier --write \"**/*.{ts,tsx,md}\"",
9 | "type:check": "turbo run type:check",
10 | "test": "infisical run --env=dev -- turbo run test || turbo run test",
11 | "test:run": "infisical run --env=dev -- turbo run test:run || turbo run test:run"
12 | },
13 | "devDependencies": {
14 | "@types/bad-words": "^3.0.3",
15 | "prettier": "^3.5.0",
16 | "turbo": "^2.4.2",
17 | "typescript": "5.7.3"
18 | },
19 | "packageManager": "pnpm@10.23.0",
20 | "engines": {
21 | "node": ">=20"
22 | },
23 | "pnpm": {
24 | "overrides": {
25 | "@babel/core": "7.28.5"
26 | }
27 | },
28 | "dependencies": {
29 | "bad-words": "^4.0.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/id/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/id",
3 | "version": "0.0.1",
4 | "private": true,
5 | "type": "module",
6 | "exports": {
7 | ".": {
8 | "import": "./dist/index.js",
9 | "require": "./dist/index.cjs"
10 | }
11 | },
12 | "main": "./dist/index.cjs",
13 | "module": "./dist/index.js",
14 | "types": "./dist/index.d.ts",
15 | "files": [
16 | "dist"
17 | ],
18 | "scripts": {
19 | "build": "bunchee",
20 | "dev": "bunchee --watch",
21 | "test": "vitest run",
22 | "test:watch": "vitest",
23 | "type:check": "tsc --noEmit"
24 | },
25 | "dependencies": {
26 | "@paralleldrive/cuid2": "^2.2.2"
27 | },
28 | "devDependencies": {
29 | "@refref/eslint-config": "workspace:*",
30 | "@refref/typescript-config": "workspace:*",
31 | "@types/node": "^22.15.3",
32 | "bunchee": "^6.4.0",
33 | "typescript": "^5.8.3",
34 | "vitest": "^3.1.4"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/apps/e2e/sanity/pages/acme/acme-dashboard-page.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 | import { AcmeBasePage } from "./acme-base-page";
3 |
4 | /**
5 | * Page object for Acme app dashboard (user logged in)
6 | * TODO: Implement in Task 3
7 | */
8 | export class AcmeDashboardPage extends AcmeBasePage {
9 | constructor(page: Page) {
10 | super(page);
11 | }
12 |
13 | /**
14 | * Navigate to the dashboard
15 | */
16 | async goto() {
17 | await super.goto("/dashboard");
18 | }
19 |
20 | /**
21 | * Get the referral widget link
22 | * TODO: Implement in Task 3
23 | */
24 | async getReferralLink() {
25 | // TODO: Implement in Task 3
26 | throw new Error("Not implemented");
27 | }
28 |
29 | /**
30 | * Verify widget is loaded
31 | * TODO: Implement in Task 3
32 | */
33 | async verifyWidgetLoaded() {
34 | // TODO: Implement in Task 3
35 | throw new Error("Not implemented");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/apps/e2e/sanity/pages/acme/acme-purchase-page.ts:
--------------------------------------------------------------------------------
1 | import { Page } from "@playwright/test";
2 | import { AcmeBasePage } from "./acme-base-page";
3 |
4 | /**
5 | * Page object for Acme app purchase flow
6 | * TODO: Implement in Task 3
7 | */
8 | export class AcmePurchasePage extends AcmeBasePage {
9 | constructor(page: Page) {
10 | super(page);
11 | }
12 |
13 | /**
14 | * Navigate to the purchase page
15 | */
16 | async goto() {
17 | await super.goto("/purchase");
18 | }
19 |
20 | /**
21 | * Complete a purchase
22 | * TODO: Implement in Task 3
23 | */
24 | async completePurchase(amount: number) {
25 | // TODO: Implement in Task 3
26 | throw new Error("Not implemented");
27 | }
28 |
29 | /**
30 | * Verify purchase event was tracked
31 | * TODO: Implement in Task 3
32 | */
33 | async verifyPurchaseTracked() {
34 | // TODO: Implement in Task 3
35 | throw new Error("Not implemented");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { getAuth } from "@refref/auth";
2 | import { db, schema } from "@/server/db";
3 | import { env } from "@/env";
4 | import { logger } from "@/lib/logger";
5 | import { posthog } from "@/lib/posthog";
6 |
7 | // Create auth instance using the factory function from @refref/auth
8 | export const auth = getAuth({
9 | baseURL: env.NEXT_PUBLIC_APP_URL,
10 | resendApiKey: env.RESEND_API_KEY || "debug_key",
11 | db,
12 | schema,
13 | enabledSocialAuth: env.NEXT_PUBLIC_ENABLED_SOCIAL_AUTH,
14 | enablePasswordAuth: env.NEXT_PUBLIC_ENABLE_PASSWORD_AUTH,
15 | enableMagicLinkAuth: env.NEXT_PUBLIC_ENABLE_MAGIC_LINK_AUTH,
16 | google: env.GOOGLE_CLIENT_ID
17 | ? {
18 | clientId: env.GOOGLE_CLIENT_ID,
19 | clientSecret: env.GOOGLE_CLIENT_SECRET,
20 | }
21 | : undefined,
22 | logger,
23 | posthog,
24 | trustedOrigins: [env.NEXT_PUBLIC_APP_URL],
25 | emailFrom: env.NOTIFICATIONS_EMAIL_FROM,
26 | });
27 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/settings/product/general/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Separator } from "@refref/ui/components/separator";
4 | import { UpdateProductNameCard } from "@/components/settings/product/update-product-name-card";
5 | import { UpdateProductUrlCard } from "@/components/settings/product/update-product-url-card";
6 |
7 | export default function ProductGeneralSettings() {
8 | return (
9 |
10 |
11 |
Product
12 |
13 | Manage your product information and settings
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/packages/ui/src/components/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
4 |
5 | function Collapsible({
6 | ...props
7 | }: React.ComponentProps) {
8 | return ;
9 | }
10 |
11 | function CollapsibleTrigger({
12 | ...props
13 | }: React.ComponentProps) {
14 | return (
15 |
19 | );
20 | }
21 |
22 | function CollapsibleContent({
23 | ...props
24 | }: React.ComponentProps) {
25 | return (
26 |
30 | );
31 | }
32 |
33 | export { Collapsible, CollapsibleTrigger, CollapsibleContent };
34 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/forms/notification-config-schema.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | export const notificationConfigSchema = z.object({
4 | welcomeEmail: z.object({
5 | enabled: z.boolean(),
6 | subject: z.string(),
7 | template: z.string(),
8 | }),
9 | successEmail: z.object({
10 | enabled: z.boolean(),
11 | subject: z.string(),
12 | template: z.string(),
13 | }),
14 | inApp: z.object({
15 | progressUpdates: z.boolean(),
16 | rewardNotifications: z.boolean(),
17 | }),
18 | });
19 |
20 | export type NotificationConfigType = z.infer;
21 |
22 | export const defaultNotificationConfig: NotificationConfigType = {
23 | welcomeEmail: {
24 | enabled: false,
25 | subject: "",
26 | template: "",
27 | },
28 | successEmail: {
29 | enabled: false,
30 | subject: "",
31 | template: "",
32 | },
33 | inApp: {
34 | progressUpdates: false,
35 | rewardNotifications: false,
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Platform Docs",
3 | "description": "Using RefRef",
4 | "root": true,
5 | "icon": "BookText",
6 | "pages": [
7 | "---Getting Started---",
8 | "index",
9 | "glossary",
10 | "---Setup---",
11 | "setup-referral-program",
12 | "setup-affiliate-program",
13 | "---Concepts---",
14 | "projects",
15 | "participants",
16 | "programs",
17 | "qualifying-events",
18 | "rewards",
19 | "referrer-portal",
20 | "partner-portal",
21 | "fraud-prevention",
22 | "---Integrations---",
23 | "stripe",
24 | "lemonsqueezy",
25 | "chargebee",
26 | "paddle",
27 | "paypal",
28 | "tango",
29 | "---Guides---",
30 | "b2b-saas",
31 | "b2c-subscription",
32 | "financial",
33 | "ecommerce",
34 | "education",
35 | "newsletter",
36 | "---Open Source---",
37 | "self-hosting",
38 | "contributing",
39 | "sponsoring"
40 | ]
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/apps/acme/src/app/api/logout/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { deleteSession } from "@/lib/state";
3 |
4 | export async function POST(request: NextRequest) {
5 | try {
6 | // Get session cookie
7 | const sessionId = request.cookies.get("session")?.value;
8 |
9 | if (sessionId) {
10 | // Delete session from store
11 | deleteSession(sessionId);
12 | }
13 |
14 | // Create response
15 | const response = NextResponse.json({ success: true });
16 |
17 | // Clear session cookie
18 | response.cookies.set("session", "", {
19 | httpOnly: true,
20 | secure: process.env.NODE_ENV === "production",
21 | sameSite: "lax",
22 | maxAge: 0,
23 | path: "/",
24 | });
25 |
26 | return response;
27 | } catch (error) {
28 | console.error("Logout error:", error);
29 | return NextResponse.json(
30 | { error: "Internal server error" },
31 | { status: 500 },
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/ui/src/components/kbd.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@refref/ui/lib/utils";
2 |
3 | function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
4 | return (
5 |
15 | );
16 | }
17 |
18 | function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
19 | return (
20 |
25 | );
26 | }
27 |
28 | export { Kbd, KbdGroup };
29 |
--------------------------------------------------------------------------------
/packages/widget/src/lib/config.ts:
--------------------------------------------------------------------------------
1 | import { WidgetConfigType } from "@refref/types";
2 |
3 | export const defaultConfig: WidgetConfigType = {
4 | position: "bottom-left",
5 | triggerText: "Refer & Earn",
6 | icon: "gift",
7 | title: "Invite your friends",
8 | subtitle: "Share your referral link and earn rewards when your friends join!",
9 | logoUrl: "",
10 | shareMessage: "Join me on {productName} and get a reward!",
11 | enabledPlatforms: {
12 | facebook: true,
13 | twitter: true,
14 | linkedin: true,
15 | whatsapp: true,
16 | email: true,
17 | instagram: false,
18 | telegram: false,
19 | },
20 | referralLink: "https://i.refref.ai/",
21 | productName: "YourSaaS",
22 | };
23 |
24 | export async function fetchConfig(
25 | clientKey: string,
26 | ): Promise {
27 | // TODO: Replace with actual API call
28 | return new Promise((resolve) => {
29 | setTimeout(() => {
30 | // resolve(defaultConfig);
31 | }, 500);
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/src/types.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { signupTrackingSchema } from "./types";
3 |
4 | describe("Type Validation", () => {
5 | it("should validate valid signup payload", () => {
6 | const validPayload = {
7 | timestamp: "2024-01-01T00:00:00Z",
8 | productId: "prod-123",
9 | payload: {
10 | userId: "user-789",
11 | refcode: "REF123",
12 | email: "test@example.com",
13 | },
14 | };
15 |
16 | const result = signupTrackingSchema.safeParse(validPayload);
17 | expect(result.success).toBe(true);
18 | });
19 |
20 | it("should reject invalid payload", () => {
21 | const invalidPayload = {
22 | // Missing required fields
23 | timestamp: "2024-01-01T00:00:00Z",
24 | payload: {
25 | email: "test@example.com",
26 | },
27 | };
28 |
29 | const result = signupTrackingSchema.safeParse(invalidPayload);
30 | expect(result.success).toBe(false);
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/date-display.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Tooltip,
5 | TooltipContent,
6 | TooltipTrigger,
7 | } from "@refref/ui/components/tooltip";
8 | import { formatFullDateTime, formatRelativeDate } from "@/lib/date-utils";
9 |
10 | interface DateDisplayProps {
11 | date: Date | string;
12 | className?: string;
13 | }
14 |
15 | /**
16 | * Displays a date with GitHub-style formatting:
17 | * - Shows relative time for recent dates (e.g., "2 hours ago")
18 | * - Shows formatted date for older dates (e.g., "Nov 27, 2024")
19 | * - Hover to see full date/time tooltip (e.g., "Nov 27, 2024, 10:30:45 AM")
20 | */
21 | export function DateDisplay({ date, className }: DateDisplayProps) {
22 | return (
23 |
24 |
25 | {formatRelativeDate(date)}
26 |
27 |
28 | {formatFullDateTime(date)}
29 |
30 |
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/packages/utils/src/plugins/coredb.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance, FastifyPluginAsync } from "fastify";
2 | import fp from "fastify-plugin";
3 | import { type DBType } from "@refref/coredb";
4 |
5 | declare module "fastify" {
6 | interface FastifyInstance {
7 | db: DBType;
8 | }
9 | interface FastifyRequest {
10 | db: DBType;
11 | }
12 | }
13 |
14 | const coredbPlugin: FastifyPluginAsync<{ db: DBType }> = async (
15 | fastify: FastifyInstance,
16 | opts,
17 | ) => {
18 | const { db } = opts;
19 |
20 | fastify.decorate("db", db);
21 |
22 | // Decorate request with db for easier access in handlers
23 | fastify.decorateRequest("db", {
24 | getter() {
25 | return db;
26 | },
27 | });
28 |
29 | fastify.log.info("Database connection initialized");
30 |
31 | // Cleanup on server close
32 | fastify.addHook("onClose", async () => {
33 | fastify.log.info("Closing database connections");
34 | });
35 | };
36 |
37 | export default fp(coredbPlugin, {
38 | name: "coredb-plugin",
39 | });
40 |
--------------------------------------------------------------------------------
/apps/api/src/routes/health.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
2 | import { sql } from "drizzle-orm";
3 |
4 | export default async function healthRoutes(fastify: FastifyInstance) {
5 | fastify.get("/", healthHandler);
6 | fastify.get("/health", healthHandler);
7 | }
8 |
9 | async function healthHandler(request: FastifyRequest, reply: FastifyReply) {
10 | const checks: Record = {
11 | api: { ok: true },
12 | };
13 |
14 | // Check database connection
15 | try {
16 | await request.db.execute(sql`SELECT 1`);
17 | checks.database = { ok: true };
18 | } catch (error) {
19 | checks.database = {
20 | ok: false,
21 | error: error instanceof Error ? error.message : "Unknown error",
22 | };
23 | }
24 |
25 | const allOk = Object.values(checks).every((check) => check.ok);
26 |
27 | return reply.status(allOk ? 200 : 503).send({
28 | ok: allOk,
29 | service: "refref-api",
30 | checks,
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/providers/auth-ui-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { AuthUIProvider as BetterAuthUIProvider } from "@daveyplate/better-auth-ui";
4 | import { authClient } from "@/lib/auth-client";
5 | import { useRouter } from "next/navigation";
6 | import Link from "next/link";
7 | import { env } from "@/env";
8 |
9 | export function AuthUIProvider({ children }: { children: React.ReactNode }) {
10 | const router = useRouter();
11 |
12 | return (
13 | router.refresh()}
18 | Link={Link}
19 | magicLink={env.NEXT_PUBLIC_ENABLE_MAGIC_LINK_AUTH}
20 | credentials={env.NEXT_PUBLIC_ENABLE_PASSWORD_AUTH}
21 | social={{
22 | providers: env.NEXT_PUBLIC_ENABLED_SOCIAL_AUTH,
23 | }}
24 | organization={{
25 | logo: true,
26 | }}
27 | >
28 | {children}
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/sponsoring.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Sponsoring RefRef
3 | description: Support the development of RefRef through sponsorship
4 | ---
5 |
6 | # Sponsoring RefRef
7 |
8 | RefRef is an open source project that relies on community support to sustain development and growth. This guide explains how you can sponsor the project and help ensure its long-term success.
9 |
10 | ## Why Sponsor RefRef?
11 |
12 | Your sponsorship helps:
13 |
14 | - **Sustain Development** - Enable our team to dedicate more time to improving RefRef
15 | - **Accelerate Features** - Help prioritize new features and enhancements
16 | - **Improve Documentation** - Support the creation of better guides and tutorials
17 | - **Enhance Support** - Allow us to provide better community support
18 | - **Ensure Longevity** - Contribute to the long-term sustainability of the project
19 |
20 | ## Get in Touch
21 |
22 | Interested in sponsoring RefRef? [Contact us](/contact) to know more about sponsorship opportunities and how your support can make a difference.
23 |
24 |
--------------------------------------------------------------------------------
/apps/refer/src/routes/health.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
2 |
3 | export default async function healthRoutes(fastify: FastifyInstance) {
4 | // Health check endpoint - no rate limiting for monitoring systems
5 | fastify.get(
6 | "/health",
7 | {
8 | config: {
9 | rateLimit: false,
10 | },
11 | },
12 | async (_request: FastifyRequest, reply: FastifyReply) => {
13 | return reply.send({
14 | status: "ok",
15 | timestamp: new Date().toISOString(),
16 | service: "refer",
17 | });
18 | },
19 | );
20 |
21 | // Root health check - no rate limiting for monitoring systems
22 | fastify.get(
23 | "/",
24 | {
25 | config: {
26 | rateLimit: false,
27 | },
28 | },
29 | async (_request: FastifyRequest, reply: FastifyReply) => {
30 | return reply.send({
31 | status: "ok",
32 | service: "refer",
33 | message: "RefRef Refer Server",
34 | });
35 | },
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import { Hero2 } from "@/components/ui/hero2";
2 | import FaqGeneral from "@/components/ui/faq-general";
3 | import Cta1 from "@/components/ui/cta-1";
4 | import { ValueProp } from "@/components/ui/value-prop";
5 | import { WhyOSS } from "@/components/ui/why-oss";
6 | import { FeatureBento1 } from "@/components/ui/feature-bento-1";
7 | import { FeatureRewardTypes } from "@/components/ui/feature-reward-types";
8 | import { FeatureListGeneral } from "@/components/ui/feature-list-general";
9 | import { FeatureIndustryTypes } from "@/components/ui/feature-industry-types";
10 | import { SetupSteps } from "@/components/ui/setup-steps";
11 | export default function HomePage() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/utils",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "files": [
7 | "dist"
8 | ],
9 | "main": "./dist/index.js",
10 | "types": "./dist/index.d.ts",
11 | "exports": {
12 | ".": {
13 | "types": "./dist/index.d.ts",
14 | "import": "./dist/index.js"
15 | },
16 | "./plugins/coredb": {
17 | "types": "./dist/plugins/coredb.d.ts",
18 | "import": "./dist/plugins/coredb.js"
19 | }
20 | },
21 | "scripts": {
22 | "build": "tsc",
23 | "type:check": "tsc --noEmit",
24 | "lint": "eslint .",
25 | "lint:fix": "eslint . --fix",
26 | "test": "vitest run",
27 | "test:watch": "vitest"
28 | },
29 | "dependencies": {
30 | "@refref/coredb": "workspace:*",
31 | "fastify": "^5.2.0",
32 | "fastify-plugin": "^5.0.1"
33 | },
34 | "devDependencies": {
35 | "@refref/typescript-config": "workspace:*",
36 | "@types/node": "^22.10.2",
37 | "typescript": "^5.7.2",
38 | "vitest": "^2.1.9"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/apps/www/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6 | return (
7 |
18 | );
19 | }
20 |
21 | export { Input };
22 |
--------------------------------------------------------------------------------
/packages/ui/src/components/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@refref/ui/lib/utils";
4 |
5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6 | return (
7 |
18 | );
19 | }
20 |
21 | export { Input };
22 |
--------------------------------------------------------------------------------
/apps/e2e/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/e2e",
3 | "version": "1.0.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "test": "playwright test",
8 | "test:dev": "TEST_ENV=dev playwright test",
9 | "test:staging": "TEST_ENV=staging playwright test",
10 | "test:prod": "TEST_ENV=prod playwright test --grep @safe",
11 | "test:ui": "playwright test --ui",
12 | "test:debug": "playwright test --debug",
13 | "test:headed": "playwright test --headed",
14 | "cleanup-ports": "bash scripts/cleanup-ports.sh",
15 | "test:clean": "bash scripts/cleanup-ports.sh && SKIP_WEBSERVER_START=false playwright test"
16 | },
17 | "devDependencies": {
18 | "@playwright/test": "^1.48.0",
19 | "@types/node": "^20.11.5",
20 | "dotenv": "^16.4.5",
21 | "tsx": "^4.19.2",
22 | "typescript": "^5.6.3"
23 | },
24 | "dependencies": {
25 | "@refref/coredb": "workspace:*",
26 | "@refref/id": "workspace:*",
27 | "@refref/utils": "workspace:*",
28 | "drizzle-orm": "^0.37.0",
29 | "postgres": "^3.4.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/apps/www/lib/metadata.ts:
--------------------------------------------------------------------------------
1 | import { Metadata } from "next";
2 |
3 | export const baseUrl = new URL(
4 | process.env.NEXT_PUBLIC_APP_URL ?? "https://refref.ai",
5 | );
6 |
7 | export function createMetadata(metadata: Metadata): Metadata {
8 | return {
9 | ...metadata,
10 | title: {
11 | template: "%s | RefRef - Open Source Referral Management Platform",
12 | default: "RefRef - Open Source Referral Management Platform",
13 | },
14 | description:
15 | "Open Source Referral Management Platform. Launch your referral program in minutes.",
16 | metadataBase: baseUrl,
17 | openGraph: {
18 | title: "RefRef - Open Source Referral Management Platform",
19 | description:
20 | "Open Source Referral Management Platform. Launch your referral program in minutes.",
21 | type: "website",
22 | },
23 | twitter: {
24 | card: "summary_large_image",
25 | title: "RefRef - Open Source Referral Management Platform",
26 | description:
27 | "Open Source Referral Management Platform. Launch your referral program in minutes.",
28 | },
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/packages/ui/src/components/data-table/data-table-advanced-toolbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { Table } from "@tanstack/react-table";
4 | import type * as React from "react";
5 |
6 | import { DataTableViewOptions } from "@refref/ui/components/data-table/data-table-view-options";
7 | import { cn } from "@refref/ui/lib/utils";
8 |
9 | interface DataTableAdvancedToolbarProps
10 | extends React.ComponentProps<"div"> {
11 | table: Table;
12 | }
13 |
14 | export function DataTableAdvancedToolbar({
15 | table,
16 | children,
17 | className,
18 | ...props
19 | }: DataTableAdvancedToolbarProps) {
20 | return (
21 |
30 |
{children}
31 |
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/src/utils/cookie.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | import { extractRefcodeFromRequest } from "./cookie";
3 |
4 | describe("Cookie Utilities", () => {
5 | it("should extract refcode from cookie", () => {
6 | const request = {
7 | headers: {
8 | cookie: "refref_refcode=ABC123; session=xyz",
9 | },
10 | };
11 |
12 | const refcode = extractRefcodeFromRequest(request);
13 | expect(refcode).toBe("ABC123");
14 | });
15 |
16 | it("should return undefined when cookie not found", () => {
17 | const request = {
18 | headers: {
19 | cookie: "session=xyz; other=value",
20 | },
21 | };
22 |
23 | const refcode = extractRefcodeFromRequest(request);
24 | expect(refcode).toBeUndefined();
25 | });
26 |
27 | it("should use custom cookie name", () => {
28 | const request = {
29 | headers: {
30 | cookie: "custom_ref=CUSTOM123; refref_refcode=WRONG",
31 | },
32 | };
33 |
34 | const refcode = extractRefcodeFromRequest(request, "custom_ref");
35 | expect(refcode).toBe("CUSTOM123");
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/packages/widget/src/widget/components/widget-container.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { Widget } from "./widget";
3 | import { widgetStore } from "../../lib/store";
4 | import { useStore } from "zustand";
5 |
6 | export function WidgetContainer() {
7 | const { initialized, widgetElementSelector, setIsOpen } =
8 | useStore(widgetStore);
9 |
10 | useEffect(() => {
11 | if (!initialized) return;
12 | // Setup data attribute triggers
13 | const setupTriggers = () => {
14 | if (!widgetElementSelector) return;
15 | document.querySelectorAll(widgetElementSelector).forEach((element) => {
16 | element.addEventListener("click", () => {
17 | setIsOpen(true);
18 | });
19 | });
20 | };
21 |
22 | setupTriggers();
23 | const observer = new MutationObserver(setupTriggers);
24 | observer.observe(document.body, { childList: true, subtree: true });
25 |
26 | return () => {
27 | observer.disconnect();
28 | };
29 | }, [initialized, widgetElementSelector]);
30 |
31 | if (!initialized) {
32 | return null;
33 | }
34 |
35 | return ;
36 | }
37 |
--------------------------------------------------------------------------------
/apps/www/app/(home)/blog/[slug]/page.client.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Share } from "lucide-react";
3 | import {
4 | TooltipContent,
5 | Tooltip,
6 | TooltipTrigger,
7 | } from "@radix-ui/react-tooltip";
8 | import { useState } from "react";
9 | import { cn } from "@/lib/cn";
10 | import { buttonVariants } from "@/components/ui/button";
11 |
12 | export function Control({ url }: { url: string }): React.ReactElement {
13 | const [open, setOpen] = useState(false);
14 | const onClick = (): void => {
15 | setOpen(true);
16 | void navigator.clipboard.writeText(`${window.location.origin}${url}`);
17 | };
18 |
19 | return (
20 |
21 |
27 |
28 | Share Post
29 |
30 |
31 | Copied
32 |
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/site-header.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@refref/ui/components/button";
2 | import { Separator } from "@refref/ui/components/separator";
3 | import { SidebarTrigger } from "@refref/ui/components/sidebar";
4 | import { ReactNode } from "react";
5 |
6 | interface SiteHeaderProps {
7 | breadcrumbs?: ReactNode;
8 | meta?: ReactNode;
9 | }
10 |
11 | export function SiteHeader({ breadcrumbs, meta }: SiteHeaderProps) {
12 | return (
13 |
14 |
15 |
16 |
20 | {breadcrumbs}
21 |
{meta}
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/apps/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/assets",
3 | "version": "0.1.0",
4 | "private": true,
5 | "description": "Static assets service for RefRef scripts (attribution, widget)",
6 | "type": "module",
7 | "scripts": {
8 | "build": "tsx scripts/build.ts",
9 | "build:prod": "tsx scripts/build.ts --prod",
10 | "clean": "rm -rf public/*.js public/*.map",
11 | "dev": "infisical run --env=dev -- concurrently \"tsx scripts/build.ts --watch\" \"wrangler dev --port 8787\" || concurrently \"tsx scripts/build.ts --watch\" \"wrangler dev --port 8787\"",
12 | "deploy:cloudflare": "pnpm build:prod && wrangler deploy",
13 | "deploy:cloudflare:dev": "pnpm build && wrangler deploy --env dev"
14 | },
15 | "devDependencies": {
16 | "@cloudflare/workers-types": "^4.20250115.0",
17 | "@refref/attribution-script": "workspace:*",
18 | "@refref/typescript-config": "workspace:*",
19 | "@refref/widget": "workspace:*",
20 | "@types/node": "^24.9.1",
21 | "chokidar": "^4.0.1",
22 | "concurrently": "^9.1.0",
23 | "execa": "^9.5.2",
24 | "tsx": "^4.7.1",
25 | "typescript": "^5.9.2",
26 | "wrangler": "^4.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/api/trpc/[trpc]/route.ts:
--------------------------------------------------------------------------------
1 | import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
2 | import { type NextRequest } from "next/server";
3 |
4 | import { env } from "@/env";
5 | import { appRouter } from "@/server/api/root";
6 | import { createTRPCContext } from "@/server/api/trpc";
7 |
8 | /**
9 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
10 | * handling a HTTP request (e.g. when you make requests from Client Components).
11 | */
12 | const createContext = async (req: NextRequest) => {
13 | return createTRPCContext({
14 | headers: req.headers,
15 | });
16 | };
17 |
18 | const handler = (req: NextRequest) =>
19 | fetchRequestHandler({
20 | endpoint: "/api/trpc",
21 | req,
22 | router: appRouter,
23 | createContext: () => createContext(req),
24 | onError:
25 | env.NODE_ENV === "development"
26 | ? ({ path, error }) => {
27 | console.error(
28 | `❌ tRPC failed on ${path ?? ""}: ${error.message}`,
29 | );
30 | }
31 | : undefined,
32 | });
33 |
34 | export { handler as GET, handler as POST };
35 |
--------------------------------------------------------------------------------
/apps/acme/src/lib/refref-events.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * RefRef event tracking helpers
3 | * Sends events to RefRef API for referral attribution
4 | */
5 |
6 | // TODO: Implement proper event tracking
7 | // This requires a proper API key to be available in the UI/runtime config.
8 | // The current implementation is a placeholder and will not work.
9 | // Event tracking should also check for referral context (e.g., ref_code from cookies)
10 | // and include it in the API call.
11 |
12 | /**
13 | * Track signup event
14 | */
15 | export async function trackSignup(
16 | userEmail: string,
17 | userName: string,
18 | ): Promise {
19 | console.log(`[TODO] Track signup event for ${userEmail} (${userName})`);
20 | // Placeholder for future implementation
21 | return Promise.resolve();
22 | }
23 |
24 | /**
25 | * Track purchase event
26 | */
27 | export async function trackPurchase(
28 | userEmail: string,
29 | amount: number,
30 | productName?: string,
31 | ): Promise {
32 | console.log(
33 | `[TODO] Track purchase event for ${userEmail} ($${amount} for ${productName})`,
34 | );
35 | // Placeholder for future implementation
36 | return Promise.resolve();
37 | }
38 |
--------------------------------------------------------------------------------
/apps/acme/src/app/api/test/reset/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from "next/server";
2 | import { clearAllData } from "@/lib/state";
3 | import { clearRefRefConfig } from "@/lib/refref-runtime-config";
4 | import { cookies } from "next/headers";
5 |
6 | /**
7 | * Test-only endpoint to reset all in-memory data
8 | * Only available in development environment
9 | */
10 | export async function POST() {
11 | // Only allow in development
12 | if (process.env.NODE_ENV === "production") {
13 | return NextResponse.json(
14 | { error: "Not available in production" },
15 | { status: 403 },
16 | );
17 | }
18 |
19 | try {
20 | clearAllData();
21 | clearRefRefConfig(); // Also clear RefRef configuration
22 |
23 | // Clear configuration cookies
24 | const cookieStore = await cookies();
25 | cookieStore.delete("refref-config");
26 | cookieStore.delete("refref-secret");
27 |
28 | return NextResponse.json({ success: true, message: "All data cleared" });
29 | } catch (error) {
30 | console.error("Reset error:", error);
31 | return NextResponse.json(
32 | { error: "Failed to reset data" },
33 | { status: 500 },
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/(authenticated)/(core)/analytics/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | import { api } from "@/trpc/server";
3 |
4 | export default async function AnalyticsPage() {
5 | const programs = await api.program.getAll();
6 |
7 | if (programs.length === 0) {
8 | return (
9 |
10 |
11 |
No programs yet
12 |
13 | Create your first program to view analytics
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | const firstProgram = programs[0];
21 | if (!firstProgram) {
22 | return (
23 |
24 |
25 |
No programs found
26 |
27 | Something went wrong loading programs
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | redirect(`/analytics/${firstProgram.id}/performance`);
35 | }
36 |
--------------------------------------------------------------------------------
/apps/webapp/src/trpc/server.ts:
--------------------------------------------------------------------------------
1 | import "server-only";
2 |
3 | import { createHydrationHelpers } from "@trpc/react-query/rsc";
4 | import { headers } from "next/headers";
5 | import { cache } from "react";
6 |
7 | import { createCaller, type AppRouter } from "@/server/api/root";
8 | import { createTRPCContext } from "@/server/api/trpc";
9 | import { createQueryClient } from "./query-client";
10 |
11 | /**
12 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
13 | * handling a tRPC call from a React Server Component.
14 | */
15 | const createContext = cache(async () => {
16 | const heads = new Headers(await headers());
17 | heads.set("x-trpc-source", "rsc");
18 |
19 | return createTRPCContext({
20 | headers: heads,
21 | });
22 | });
23 |
24 | const getQueryClient = cache(createQueryClient);
25 | const caller = createCaller(createContext);
26 |
27 | const helpers = createHydrationHelpers(caller, getQueryClient);
28 |
29 | // Type assertion to workaround tRPC v11 type inference issue
30 | export const api = helpers.trpc as ReturnType<
31 | typeof createHydrationHelpers
32 | >["trpc"];
33 | export const HydrateClient = helpers.HydrateClient;
34 |
--------------------------------------------------------------------------------
/packages/ui/src/components/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | CircleCheckIcon,
5 | InfoIcon,
6 | Loader2Icon,
7 | OctagonXIcon,
8 | TriangleAlertIcon,
9 | } from "lucide-react";
10 | import { useTheme } from "next-themes";
11 | import { Toaster as Sonner, type ToasterProps } from "sonner";
12 |
13 | const Toaster = ({ ...props }: ToasterProps) => {
14 | const { theme = "system" } = useTheme();
15 |
16 | return (
17 | ,
22 | info: ,
23 | warning: ,
24 | error: ,
25 | loading: ,
26 | }}
27 | style={
28 | {
29 | "--normal-bg": "var(--popover)",
30 | "--normal-text": "var(--popover-foreground)",
31 | "--normal-border": "var(--border)",
32 | "--border-radius": "var(--radius)",
33 | } as React.CSSProperties
34 | }
35 | {...props}
36 | />
37 | );
38 | };
39 |
40 | export { Toaster };
41 |
--------------------------------------------------------------------------------
/apps/www/components/ui/animated-gradient-text.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentPropsWithoutRef, ReactNode } from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export interface AnimatedGradientTextProps
6 | extends ComponentPropsWithoutRef<"div"> {
7 | children: ReactNode;
8 | }
9 |
10 | export function AnimatedGradientText({
11 | children,
12 | className,
13 | ...props
14 | }: AnimatedGradientTextProps) {
15 | return (
16 |
23 |
26 |
27 | {children}
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/apps/acme/src/app/api/me/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | import { getSession, getUserById } from "@/lib/state";
3 |
4 | export async function GET(request: NextRequest) {
5 | try {
6 | // Get session cookie
7 | const sessionId = request.cookies.get("session")?.value;
8 |
9 | if (!sessionId) {
10 | return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
11 | }
12 |
13 | // Get session
14 | const session = getSession(sessionId);
15 | if (!session) {
16 | return NextResponse.json(
17 | { error: "Invalid or expired session" },
18 | { status: 401 },
19 | );
20 | }
21 |
22 | // Get user
23 | const user = getUserById(session.userId);
24 | if (!user) {
25 | return NextResponse.json({ error: "User not found" }, { status: 404 });
26 | }
27 |
28 | return NextResponse.json({
29 | user: {
30 | id: user.id,
31 | email: user.email,
32 | name: user.name,
33 | },
34 | });
35 | } catch (error) {
36 | console.error("Get current user error:", error);
37 | return NextResponse.json(
38 | { error: "Internal server error" },
39 | { status: 500 },
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/apps/www/app/docs/layout.tsx:
--------------------------------------------------------------------------------
1 | import { DocsLayout, type DocsLayoutProps } from "fumadocs-ui/layouts/docs";
2 | import type { ReactNode } from "react";
3 | import { baseOptions } from "@/app/layout.config";
4 | import "fumadocs-twoslash/twoslash.css";
5 | import { source } from "@/lib/source";
6 |
7 | const docsOptions: DocsLayoutProps = {
8 | ...baseOptions,
9 | tree: source.pageTree,
10 | sidebar: {
11 | tabs: {
12 | transform(option, node) {
13 | const meta = source.getNodeMeta(node);
14 | if (!meta) return option;
15 |
16 | return {
17 | ...option,
18 | icon: (
19 |
26 | {node.icon}
27 |
28 | ),
29 | };
30 | },
31 | },
32 | },
33 | };
34 |
35 | export default function Layout({ children }: { children: ReactNode }) {
36 | return {children};
37 | }
38 |
--------------------------------------------------------------------------------
/packages/email-templates/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { MagicLinkEmail } from "./templates/MagicLinkEmail.js";
3 | import { InvitationEmail } from "./templates/InvitationEmail.js";
4 | import { renderEmail } from "./utils/render.js";
5 |
6 | /**
7 | * Generates HTML for a magic link email
8 | * @param magicLink - The magic link URL for authentication
9 | * @returns HTML string ready to be sent via email
10 | */
11 | export async function renderMagicLinkEmail(magicLink: string): Promise {
12 | return await renderEmail(React.createElement(MagicLinkEmail, { magicLink }));
13 | }
14 |
15 | /**
16 | * Generates HTML for an invitation email
17 | * @param params - Invitation parameters
18 | * @returns HTML string ready to be sent via email
19 | */
20 | export async function renderInvitationEmail(params: {
21 | role: string;
22 | inviterName?: string;
23 | inviterEmail?: string;
24 | inviteLink: string;
25 | }): Promise {
26 | return await renderEmail(React.createElement(InvitationEmail, params));
27 | }
28 |
29 | // Export the template components for advanced use cases
30 | export { MagicLinkEmail } from "./templates/MagicLinkEmail.js";
31 | export { InvitationEmail } from "./templates/InvitationEmail.js";
32 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/providers/posthog-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { PostHogProvider as PHProvider } from "posthog-js/react";
4 | import posthog from "posthog-js";
5 | import { useSession } from "@/lib/auth-client";
6 | import { useEffect } from "react";
7 |
8 | function PostHogIdentifier() {
9 | const { data: session, isPending } = useSession();
10 |
11 | useEffect(() => {
12 | // Skip if session is still loading
13 | if (isPending) {
14 | return;
15 | }
16 |
17 | // Identify user when authenticated
18 | if (session?.user) {
19 | posthog.identify(session.user.id, {
20 | email: session.user.email,
21 | name: session.user.name,
22 | ...(session.session?.activeOrganizationId && {
23 | activeOrganizationId: session.session.activeOrganizationId,
24 | }),
25 | });
26 | } else {
27 | // Reset PostHog when user logs out
28 | posthog.reset();
29 | }
30 | }, [session, isPending]);
31 |
32 | return null;
33 | }
34 |
35 | export function PostHogProvider({ children }: { children: React.ReactNode }) {
36 | return (
37 |
38 |
39 | {children}
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/participants.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Participants
3 | description: Managing participants in your RefRef referral and affiliate programs
4 | ---
5 | Participants are the individuals or organizations who refer others to your business through RefRef's referral and affiliate programs.
6 |
7 | ## Types of Participants
8 |
9 | RefRef supports various types of participants:
10 |
11 | - **Referrers** - Existing customers who refer friends and colleagues
12 | - **Affiliates** - External partners who promote your products for commission
13 | - **Advocates** - Brand enthusiasts who drive word-of-mouth marketing
14 | - **Partners** - Business partners with formal referral arrangements
15 | - **Influencers** - Content creators with audience reach
16 |
17 | ## Participant Management
18 |
19 | The Participants dashboard allows you to:
20 |
21 | 1. **View and Filter** - See all participants across programs or filter by specific criteria
22 | 2. **Approve Applications** - Review and approve new participant applications
23 | 3. **Track Performance** - Monitor referral activity, conversions, and earnings
24 | 4. **Communicate** - Send announcements, updates, or personalized messages
25 | 5. **Manage Payouts** - Process commission payments and view payment history
--------------------------------------------------------------------------------
/packages/eslint-config/react-internal.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import tseslint from "typescript-eslint";
4 | import pluginReactHooks from "eslint-plugin-react-hooks";
5 | import pluginReact from "eslint-plugin-react";
6 | import globals from "globals";
7 | import { config as baseConfig } from "./base.js";
8 |
9 | /**
10 | * A custom ESLint configuration for libraries that use React.
11 | *
12 | * @type {import("eslint").Linter.Config} */
13 | export const config = [
14 | ...baseConfig,
15 | js.configs.recommended,
16 | eslintConfigPrettier,
17 | ...tseslint.configs.recommended,
18 | pluginReact.configs.flat.recommended,
19 | {
20 | languageOptions: {
21 | ...pluginReact.configs.flat.recommended.languageOptions,
22 | globals: {
23 | ...globals.serviceworker,
24 | ...globals.browser,
25 | },
26 | },
27 | },
28 | {
29 | plugins: {
30 | "react-hooks": pluginReactHooks,
31 | },
32 | settings: { react: { version: "detect" } },
33 | rules: {
34 | ...pluginReactHooks.configs.recommended.rules,
35 | // React scope no longer necessary with new JSX transform.
36 | "react/react-in-jsx-scope": "off",
37 | },
38 | },
39 | ];
40 |
--------------------------------------------------------------------------------
/packages/ui/src/components/referral-widget/referral-widget-dialog-trigger.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@refref/ui/lib/utils";
2 | import { Button } from "@refref/ui/components/button";
3 | import { WidgetConfigType } from "@refref/types";
4 | import { Gift, Zap, Star, Heart } from "lucide-react";
5 |
6 | type Props = {
7 | className?: string;
8 | config: WidgetConfigType;
9 | onOpenChange: (open: boolean) => void;
10 | };
11 |
12 | const iconMap = {
13 | gift: Gift,
14 | heart: Heart,
15 | star: Star,
16 | zap: Zap,
17 | };
18 |
19 | export function ReferralWidgetDialogTrigger({
20 | config,
21 | onOpenChange,
22 | className,
23 | }: Props) {
24 | const IconComponent = iconMap[config.icon as keyof typeof iconMap] || Gift;
25 |
26 | return (
27 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/program-setup-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { ReactNode } from "react";
4 | import { ExternalLink } from "lucide-react";
5 | import Link from "next/link";
6 | import { cn } from "@/lib/utils";
7 |
8 | interface SetupCardProps {
9 | title: string;
10 | docLink?: string;
11 | className?: string;
12 | onClick?: () => void;
13 | isActive: boolean;
14 | }
15 |
16 | export function SetupCard({
17 | title,
18 | docLink,
19 | className,
20 | onClick,
21 | isActive,
22 | }: SetupCardProps) {
23 | return (
24 |
32 | {title}
33 | {docLink && (
34 | e.stopPropagation()}
38 | className="ml-auto"
39 | >
40 |
41 | Documentation
42 |
43 | )}
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/packages/email-templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/email-templates",
3 | "version": "0.0.0",
4 | "private": true,
5 | "type": "module",
6 | "files": [
7 | "dist"
8 | ],
9 | "main": "./dist/cjs/index.cjs",
10 | "module": "./dist/es/index.js",
11 | "types": "./dist/es/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": {
15 | "types": "./dist/es/index.d.ts",
16 | "default": "./dist/es/index.js"
17 | },
18 | "require": {
19 | "types": "./dist/cjs/index.d.cts",
20 | "default": "./dist/cjs/index.cjs"
21 | }
22 | }
23 | },
24 | "scripts": {
25 | "build": "bunchee",
26 | "clean": "rm -rf dist",
27 | "dev": "email dev --dir ./src/templates -p 3005",
28 | "lint": "eslint src/",
29 | "type:check": "tsc --noEmit"
30 | },
31 | "dependencies": {
32 | "@react-email/components": "^0.0.29",
33 | "@react-email/render": "^1.0.3",
34 | "react": "^19.2.0"
35 | },
36 | "devDependencies": {
37 | "@refref/eslint-config": "workspace:*",
38 | "@refref/typescript-config": "workspace:*",
39 | "@types/node": "^22.15.3",
40 | "@types/react": "^19.2.0",
41 | "bunchee": "^6.4.0",
42 | "eslint": "^9.26.0",
43 | "react-email": "^3.0.3",
44 | "typescript": "5.8.3"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/auth/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/auth",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "private": true,
6 | "files": [
7 | "dist"
8 | ],
9 | "main": "./dist/es/index.js",
10 | "module": "./dist/es/index.js",
11 | "types": "./dist/es/index.d.ts",
12 | "exports": {
13 | ".": {
14 | "import": {
15 | "types": "./dist/es/index.d.ts",
16 | "default": "./dist/es/index.js"
17 | },
18 | "require": {
19 | "types": "./dist/cjs/index.d.cts",
20 | "default": "./dist/cjs/index.cjs"
21 | }
22 | }
23 | },
24 | "scripts": {
25 | "build": "bunchee",
26 | "clean": "rm -rf dist",
27 | "lint": "eslint src/",
28 | "type:check": "tsc --noEmit"
29 | },
30 | "devDependencies": {
31 | "@refref/eslint-config": "workspace:*",
32 | "@refref/typescript-config": "workspace:*",
33 | "@types/node": "^22.15.3",
34 | "bunchee": "^6.4.0",
35 | "eslint": "^9.26.0",
36 | "typescript": "5.8.3"
37 | },
38 | "dependencies": {
39 | "@refref/coredb": "workspace:*",
40 | "@refref/email-templates": "workspace:*",
41 | "@refref/utils": "workspace:*",
42 | "@refref/types": "workspace:*",
43 | "better-auth": "^1.3.34",
44 | "resend": "^4.6.0",
45 | "drizzle-orm": "0.44.7"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "ui": "tui",
4 | "globalEnv": [
5 | "BETTER_AUTH_SECRET",
6 | "DATABASE_URL",
7 | "NEXT_PUBLIC_APP_URL",
8 | "NEXT_PUBLIC_ASSETS_URL",
9 | "BETTER_AUTH_URL",
10 | "NODE_ENV",
11 | "RESEND_API_KEY",
12 | "GOOGLE_CLIENT_ID",
13 | "GOOGLE_CLIENT_SECRET",
14 | "REFERRAL_PROGRAM_CLIENT_ID",
15 | "REFERRAL_PROGRAM_CLIENT_SECRET",
16 | "NEXT_PUBLIC_REFREF_PRODUCT_ID",
17 | "NEXT_PUBLIC_REFREF_PROGRAM_ID",
18 | "VITE_REFREF_API_ENDPOINT"
19 | ],
20 | "tasks": {
21 | "build": {
22 | "dependsOn": ["^build"],
23 | "inputs": ["$TURBO_DEFAULT$", ".env*"],
24 | "outputs": [".next/**", "!.next/cache/**", "dist/**"],
25 | "env": []
26 | },
27 | "lint": {
28 | "dependsOn": ["^lint"]
29 | },
30 | "type:check": {
31 | "dependsOn": ["^build", "^type:check"]
32 | },
33 | "test": {
34 | "dependsOn": ["^build"]
35 | },
36 | "test:run": {
37 | "dependsOn": ["^build"]
38 | },
39 | "dev": {
40 | "dependsOn": ["^build"],
41 | "cache": false,
42 | "persistent": true
43 | },
44 | "db:generate": {},
45 | "db:migrate": {},
46 | "db:push": {},
47 | "db:seed": {},
48 | "db:studio": {}
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/apps/www/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5 | import { CheckIcon } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | function Checkbox({
10 | className,
11 | ...props
12 | }: React.ComponentProps) {
13 | return (
14 |
22 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export { Checkbox };
33 |
--------------------------------------------------------------------------------
/packages/utils/README.md:
--------------------------------------------------------------------------------
1 | # @refref/utils
2 |
3 | Shared utilities and plugins for RefRef applications.
4 |
5 | ## Plugins
6 |
7 | ### CoreDB Plugin
8 |
9 | Fastify plugin that provides database connectivity using `@refref/coredb`.
10 |
11 | #### Features
12 |
13 | - Initializes database connection pool at server startup
14 | - Decorates Fastify instance with `db` property
15 | - Decorates request with `db` property for easy access
16 | - Handles graceful shutdown and cleanup
17 | - TypeScript-safe with proper type declarations
18 |
19 | #### Usage
20 |
21 | ```typescript
22 | import Fastify from "fastify";
23 | import { coredbPlugin } from "@refref/utils";
24 |
25 | const app = Fastify();
26 |
27 | // Register the plugin
28 | await app.register(coredbPlugin);
29 |
30 | // Use in handlers via request.db
31 | app.get("/example", async (request, reply) => {
32 | const result = await request.db.query.users.findMany();
33 | return result;
34 | });
35 | ```
36 |
37 | #### Environment Variables
38 |
39 | - `DATABASE_URL` (required) - PostgreSQL connection string
40 |
41 | #### Type Declarations
42 |
43 | The plugin extends Fastify types:
44 |
45 | ```typescript
46 | declare module "fastify" {
47 | interface FastifyInstance {
48 | db: DBType;
49 | }
50 | interface FastifyRequest {
51 | db: DBType;
52 | }
53 | }
54 | ```
55 |
--------------------------------------------------------------------------------
/packages/ui/src/components/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SwitchPrimitive from "@radix-ui/react-switch";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function Switch({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
21 |
27 |
28 | );
29 | }
30 |
31 | export { Switch };
32 |
--------------------------------------------------------------------------------
/packages/attribution-script/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import dts from "vite-plugin-dts";
3 | import { resolve } from "path";
4 | import path from "path";
5 |
6 | export default defineConfig(({ mode }) => ({
7 | configLoader: "runner",
8 | envDir: __dirname,
9 | resolve: {
10 | alias: {
11 | "@": path.resolve(__dirname, "./src"),
12 | },
13 | },
14 | plugins: [
15 | // to emit types for dev purposes?
16 | dts({
17 | insertTypesEntry: true,
18 | outDir: "dist/types",
19 | exclude: ["src/**/*.test.ts"],
20 | }),
21 | ],
22 | build: {
23 | target: "esnext",
24 | lib: {
25 | entry: resolve(__dirname, "src/index.ts"),
26 | name: "RefRefAttribution",
27 | fileName: (format) => `attribution-script.${format}.js`,
28 | formats: ["es", "umd"],
29 | },
30 | minify: mode === "production" ? "esbuild" : false,
31 | cssCodeSplit: false,
32 | sourcemap: mode !== "production",
33 | outDir: "dist",
34 | emptyOutDir: true,
35 | rollupOptions: {
36 | output: {
37 | compact: true,
38 | generatedCode: {
39 | constBindings: true,
40 | },
41 | },
42 | treeshake: {
43 | preset: "recommended",
44 | moduleSideEffects: false,
45 | },
46 | },
47 | },
48 | }));
49 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/auth-client.ts:
--------------------------------------------------------------------------------
1 | import { createAuthClient } from "better-auth/react";
2 | import {
3 | adminClient,
4 | apiKeyClient,
5 | magicLinkClient,
6 | organizationClient,
7 | } from "better-auth/client/plugins";
8 | import { env } from "@/env";
9 |
10 | /**
11 | * BetterAuth client setup with organization plugin for React components
12 | */
13 | export const authClient = createAuthClient({
14 | // Base URL should match the server's baseURL
15 | baseURL: env.NEXT_PUBLIC_APP_URL,
16 |
17 | // Add the organization client plugin
18 | plugins: [
19 | adminClient(),
20 | apiKeyClient(),
21 | organizationClient(),
22 | magicLinkClient(),
23 | ],
24 | });
25 |
26 | // Export commonly used auth client methods
27 | export const {
28 | // Session management
29 | useSession,
30 | getSession,
31 |
32 | // Authentication methods
33 | signIn,
34 | signUp,
35 | signOut,
36 |
37 | // Organization-related hooks and methods
38 | useListOrganizations,
39 | useActiveOrganization,
40 |
41 | // Organization management methods
42 | organization,
43 | } = authClient;
44 |
45 | // Export specific organization methods for convenience
46 | export const {
47 | inviteMember,
48 | cancelInvitation,
49 | updateMemberRole,
50 | removeMember,
51 | listInvitations,
52 | acceptInvitation,
53 | } = authClient.organization;
54 |
--------------------------------------------------------------------------------
/apps/webapp/src/lib/date-utils.ts:
--------------------------------------------------------------------------------
1 | import { differenceInDays, format, formatDistanceToNow } from "date-fns";
2 |
3 | /**
4 | * Formats a date for display with smart relative/absolute formatting.
5 | * - Dates within 7 days: "2 hours ago", "3 days ago"
6 | * - Dates older than 7 days: "Nov 27, 2024"
7 | *
8 | * @param date - The date to format (Date object or ISO string)
9 | * @returns Formatted date string
10 | */
11 | export function formatRelativeDate(date: Date | string): string {
12 | const dateObj = typeof date === "string" ? new Date(date) : date;
13 | const now = new Date();
14 | const daysDifference = differenceInDays(now, dateObj);
15 |
16 | // For dates within 7 days, show relative time
17 | if (daysDifference < 7) {
18 | return formatDistanceToNow(dateObj, { addSuffix: true });
19 | }
20 |
21 | // For older dates, show formatted date
22 | return format(dateObj, "MMM d, yyyy");
23 | }
24 |
25 | /**
26 | * Formats a date for tooltip display with full date and time.
27 | * Format: "Nov 27, 2024, 10:30:45 AM"
28 | *
29 | * @param date - The date to format (Date object or ISO string)
30 | * @returns Full formatted date/time string
31 | */
32 | export function formatFullDateTime(date: Date | string): string {
33 | const dateObj = typeof date === "string" ? new Date(date) : date;
34 | return format(dateObj, "MMM d, yyyy, h:mm:ss a");
35 | }
36 |
--------------------------------------------------------------------------------
/packages/ui/src/components/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function Avatar({
9 | className,
10 | ...props
11 | }: React.ComponentProps) {
12 | return (
13 |
21 | );
22 | }
23 |
24 | function AvatarImage({
25 | className,
26 | ...props
27 | }: React.ComponentProps) {
28 | return (
29 |
34 | );
35 | }
36 |
37 | function AvatarFallback({
38 | className,
39 | ...props
40 | }: React.ComponentProps) {
41 | return (
42 |
50 | );
51 | }
52 |
53 | export { Avatar, AvatarImage, AvatarFallback };
54 |
--------------------------------------------------------------------------------
/packages/ui/src/components/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5 | import { CheckIcon } from "lucide-react";
6 |
7 | import { cn } from "@refref/ui/lib/utils";
8 |
9 | function Checkbox({
10 | className,
11 | ...props
12 | }: React.ComponentProps) {
13 | return (
14 |
22 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export { Checkbox };
33 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/referral-widget-init.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useRef } from "react";
4 | import { api } from "@/trpc/react";
5 | import { env } from "@/env";
6 |
7 | declare global {
8 | interface Window {
9 | RefRef: Array<[string, any]>;
10 | }
11 | }
12 |
13 | export function ReferralWidgetInit() {
14 | const isInitialized = useRef(false);
15 |
16 | // Check if referral credentials are available
17 | const isEnabled = Boolean(
18 | env.NEXT_PUBLIC_REFREF_PRODUCT_ID && env.NEXT_PUBLIC_REFREF_PROGRAM_ID,
19 | );
20 |
21 | // Only query for token if configured
22 | const { data } = api.referral.getWidgetToken.useQuery(undefined, {
23 | enabled: isEnabled,
24 | });
25 |
26 | useEffect(() => {
27 | // Skip initialization if not configured or no data
28 | if (!isEnabled || !data || isInitialized.current) return;
29 |
30 | // Initialize window.RefRef if it doesn't exist
31 | window.RefRef = window.RefRef || [];
32 |
33 | // Initialize the widget with the token from the backend
34 | window.RefRef.push([
35 | "init",
36 | {
37 | productId: env.NEXT_PUBLIC_REFREF_PRODUCT_ID,
38 | programId: env.NEXT_PUBLIC_REFREF_PROGRAM_ID,
39 | participantId: "dfsdfs",
40 | token: data.token,
41 | },
42 | ]);
43 |
44 | isInitialized.current = true;
45 | }, [data, isEnabled]);
46 |
47 | return null;
48 | }
49 |
--------------------------------------------------------------------------------
/packages/ui/src/types/data-table.ts:
--------------------------------------------------------------------------------
1 | import type { DataTableConfig } from "@refref/ui/config/data-table";
2 | import type { FilterItemSchema } from "@refref/ui/lib/parsers";
3 | import type { ColumnSort, Row, RowData } from "@tanstack/react-table";
4 |
5 | declare module "@tanstack/react-table" {
6 | // biome-ignore lint/correctness/noUnusedVariables:
7 | interface ColumnMeta {
8 | label?: string;
9 | placeholder?: string;
10 | variant?: FilterVariant;
11 | options?: Option[];
12 | range?: [number, number];
13 | unit?: string;
14 | icon?: React.FC>;
15 | }
16 | }
17 |
18 | export interface Option {
19 | label: string;
20 | value: string;
21 | count?: number;
22 | icon?: React.FC>;
23 | }
24 |
25 | export type FilterOperator = DataTableConfig["operators"][number];
26 | export type FilterVariant = DataTableConfig["filterVariants"][number];
27 | export type JoinOperator = DataTableConfig["joinOperators"][number];
28 |
29 | export interface ExtendedColumnSort extends Omit {
30 | id: Extract;
31 | }
32 |
33 | export interface ExtendedColumnFilter extends FilterItemSchema {
34 | id: Extract;
35 | }
36 |
37 | export interface DataTableRowAction {
38 | row: Row;
39 | variant: "update" | "delete";
40 | }
41 |
--------------------------------------------------------------------------------
/apps/refer/test/setup.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | // Mock the @refref/coredb module
4 | vi.mock("@refref/coredb", async (importOriginal) => {
5 | const actual = await importOriginal();
6 |
7 | const mockDb = {
8 | execute: vi.fn().mockResolvedValue({ rows: [{ "?column?": 1 }] }),
9 | select: vi.fn().mockReturnThis(),
10 | from: vi.fn().mockReturnThis(),
11 | where: vi.fn().mockReturnThis(),
12 | limit: vi.fn().mockResolvedValue([]),
13 | insert: vi.fn().mockReturnThis(),
14 | values: vi.fn().mockReturnThis(),
15 | returning: vi.fn().mockResolvedValue([]),
16 | onConflictDoUpdate: vi.fn().mockReturnThis(),
17 | onConflictDoNothing: vi.fn().mockReturnThis(),
18 | update: vi.fn().mockReturnThis(),
19 | set: vi.fn().mockReturnThis(),
20 | transaction: vi.fn(async (fn) => fn(mockDb)),
21 | query: {
22 | refcode: {
23 | findFirst: vi.fn().mockResolvedValue(null),
24 | },
25 | participant: {
26 | findFirst: vi.fn().mockResolvedValue(null),
27 | },
28 | product: {
29 | findFirst: vi.fn().mockResolvedValue(null),
30 | },
31 | reflink: {
32 | findFirst: vi.fn().mockResolvedValue(null),
33 | },
34 | },
35 | };
36 |
37 | return {
38 | createDb: vi.fn(() => mockDb),
39 | schema: actual.schema, // Re-export actual schema for type checking
40 | };
41 | });
42 |
--------------------------------------------------------------------------------
/apps/api/test/setup.ts:
--------------------------------------------------------------------------------
1 | import { vi } from "vitest";
2 |
3 | // Mock the @refref/coredb module
4 | vi.mock("@refref/coredb", async (importOriginal) => {
5 | const actual = await importOriginal();
6 |
7 | const mockDb = {
8 | execute: vi.fn().mockResolvedValue({ rows: [{ "?column?": 1 }] }),
9 | select: vi.fn().mockReturnThis(),
10 | from: vi.fn().mockReturnThis(),
11 | where: vi.fn().mockReturnThis(),
12 | limit: vi.fn().mockResolvedValue([]),
13 | insert: vi.fn().mockReturnThis(),
14 | values: vi.fn().mockReturnThis(),
15 | returning: vi.fn().mockResolvedValue([]),
16 | onConflictDoUpdate: vi.fn().mockReturnThis(),
17 | onConflictDoNothing: vi.fn().mockReturnThis(),
18 | update: vi.fn().mockReturnThis(),
19 | set: vi.fn().mockReturnThis(),
20 | transaction: vi.fn(async (fn) => fn(mockDb)),
21 | query: {
22 | program: {
23 | findFirst: vi.fn().mockResolvedValue(null),
24 | },
25 | participant: {
26 | findFirst: vi.fn().mockResolvedValue(null),
27 | },
28 | refcode: {
29 | findFirst: vi.fn().mockResolvedValue(null),
30 | },
31 | productSecrets: {
32 | findFirst: vi.fn().mockResolvedValue(null),
33 | },
34 | },
35 | };
36 |
37 | return {
38 | createDb: vi.fn(() => mockDb),
39 | schema: actual.schema, // Re-export actual schema for type checking
40 | };
41 | });
42 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | env:
10 | PNPM_VERSION: 10.4.1
11 | DATABASE_URL: "postgresql://placeholder"
12 | BETTER_AUTH_SECRET: "placeholder-secret-for-build"
13 | SKIP_ENV_VALIDATION: "true"
14 |
15 | jobs:
16 | test:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Checkout repository
21 | uses: actions/checkout@v3
22 |
23 | - name: Setup Node.js
24 | uses: actions/setup-node@v3
25 | with:
26 | node-version: 20
27 |
28 | - name: Install pnpm
29 | uses: pnpm/action-setup@v2
30 | with:
31 | version: ${{ env.PNPM_VERSION }}
32 |
33 | - name: Get pnpm store directory
34 | shell: bash
35 | run: |
36 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
37 |
38 | - name: Setup pnpm cache
39 | uses: actions/cache@v4
40 | with:
41 | path: ${{ env.STORE_PATH }}
42 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
43 | restore-keys: |
44 | ${{ runner.os }}-pnpm-store-
45 |
46 | - name: Install dependencies
47 | run: pnpm install
48 |
49 | - name: Type Check
50 | run: pnpm type:check
51 |
52 | - name: Run Tests
53 | run: pnpm test:run
54 | env:
55 | SKIP_ENV_VALIDATION: "true"
56 |
--------------------------------------------------------------------------------
/apps/www/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./global.css";
2 | import { RootProvider } from "fumadocs-ui/provider";
3 | import { Inter } from "next/font/google";
4 | import type { ReactNode } from "react";
5 | import PlausibleProvider from "next-plausible";
6 | import { GoogleTagManager } from "@next/third-parties/google";
7 | import { createMetadata } from "@/lib/metadata";
8 | import { TooltipProvider } from "@radix-ui/react-tooltip";
9 |
10 | export const metadata = createMetadata({});
11 |
12 | const inter = Inter({
13 | subsets: ["latin"],
14 | });
15 |
16 | export default function Layout({ children }: { children: ReactNode }) {
17 | return (
18 |
19 |
20 |
27 |
28 |
29 |
30 |
40 | {children}
41 |
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/apps/acme/src/lib/refref-config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * RefRef integration configuration for ACME
3 | * Service URLs are static, credentials are dynamic (set via /api/test/configure)
4 | */
5 |
6 | import { getRefRefConfigSync } from "./refref-runtime-config";
7 |
8 | export const refrefConfig = {
9 | // RefRef service URLs (static)
10 | referUrl: process.env.NEXT_PUBLIC_REFREF_REFER_URL || "http://localhost:3002",
11 | apiUrl: process.env.NEXT_PUBLIC_REFREF_API_URL || "http://localhost:3001",
12 | assetsUrl:
13 | process.env.NEXT_PUBLIC_REFREF_ASSETS_URL || "http://localhost:8787",
14 |
15 | // Product credentials (dynamic - from runtime config or fallback to env)
16 | get productId() {
17 | const config = getRefRefConfigSync();
18 | return config?.productId || "acme-product";
19 | },
20 | get clientId() {
21 | const config = getRefRefConfigSync();
22 | return config?.clientId || "test-client-id";
23 | },
24 | get clientSecret() {
25 | const config = getRefRefConfigSync();
26 | return config?.clientSecret || "test-client-secret";
27 | },
28 | get programId() {
29 | const config = getRefRefConfigSync();
30 | return config?.programId || "test-program-id";
31 | },
32 |
33 | // Script URLs (both served from assets server)
34 | get attributionScriptUrl() {
35 | return `${this.assetsUrl}/scripts/attribution.js`;
36 | },
37 | get widgetScriptUrl() {
38 | return `${this.assetsUrl}/scripts/widget.js`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/apps/webapp/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 | /**
3 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
4 | * for Docker builds.
5 | */
6 | import "@/env";
7 |
8 | const config: NextConfig = {
9 | async redirects() {
10 | return [
11 | {
12 | source: "/settings",
13 | destination: "/settings/account/profile",
14 | permanent: true,
15 | },
16 | ];
17 | },
18 | async rewrites() {
19 | return [
20 | {
21 | source: "/ingest/static/:path*",
22 | destination: "https://us-assets.i.posthog.com/static/:path*",
23 | },
24 | {
25 | source: "/ingest/:path*",
26 | destination: "https://us.i.posthog.com/:path*",
27 | },
28 | ];
29 | },
30 | skipTrailingSlashRedirect: true,
31 | transpilePackages: ["@refref/ui"],
32 | typescript: {
33 | ignoreBuildErrors: false,
34 | },
35 | webpack: (config) => {
36 | // using asset/source to load the script
37 | // causes minify etc to run and typeof window becomes undefined
38 | config.module.rules.push({
39 | test: /\.es\.js$/,
40 | use: ["raw-loader"],
41 | });
42 |
43 | return config;
44 | },
45 | turbopack: {
46 | rules: {
47 | // Configure Turbopack to handle .es.js scripts as assets
48 | "*.es.js": {
49 | loaders: ["raw-loader"],
50 | },
51 | },
52 | },
53 | };
54 |
55 | export default config;
56 |
--------------------------------------------------------------------------------
/apps/webapp/src/app/accept-invitation/[token]/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 | import { logger } from "better-auth";
3 | import { headers as getHeaders } from "next/headers";
4 | import { auth } from "@/lib/auth";
5 |
6 | export default async function AcceptInvitationPage({
7 | params,
8 | }: {
9 | params: Promise<{ token: string }>;
10 | }) {
11 | const { token } = await params;
12 | const headers = await getHeaders();
13 | const session = await auth.api.getSession({
14 | headers,
15 | });
16 | if (!session?.user) {
17 | // Redirect to signin but keep the token in the URL for after signin
18 | redirect(`/auth/sign-in?callbackUrl=/accept-invitation/${token}`);
19 | }
20 |
21 | try {
22 | // Accept the invitation server-side
23 | await auth.api.acceptInvitation({
24 | body: {
25 | invitationId: token,
26 | },
27 | headers,
28 | });
29 | } catch (error) {
30 | logger.error("Error accepting invitation", error);
31 | return (
32 |
33 |
34 | Failed to accept invitation. The invitation may be invalid or expired.
35 |
36 |
37 | );
38 | } finally {
39 | // wow cant have redirect in try catch
40 | // for ref: https://stackoverflow.com/questions/76191324/next-13-4-error-next-redirect-in-api-routes
41 | redirect("/");
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/nav-secondary.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { type Icon } from "@tabler/icons-react";
5 | import { type IconType } from "react-icons";
6 |
7 | import {
8 | SidebarGroup,
9 | SidebarGroupContent,
10 | SidebarMenu,
11 | SidebarMenuButton,
12 | SidebarMenuItem,
13 | } from "@refref/ui/components/sidebar";
14 |
15 | export function NavSecondary({
16 | items,
17 | ...props
18 | }: {
19 | items: {
20 | title: string;
21 | url: string;
22 | icon: Icon | IconType;
23 | }[];
24 | } & React.ComponentPropsWithoutRef) {
25 | return (
26 |
27 |
28 |
29 | {items.map((item) => {
30 | const isExternal = item.url.startsWith("http");
31 | const linkProps = isExternal
32 | ? { target: "_blank", rel: "noopener noreferrer" }
33 | : {};
34 |
35 | return (
36 |
37 |
38 |
39 |
40 | {item.title}
41 |
42 |
43 |
44 | );
45 | })}
46 |
47 |
48 |
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/packages/refref-better-auth/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/better-auth",
3 | "version": "0.1.0",
4 | "description": "Better Auth integration package for RefRef referral tracking",
5 | "main": "./dist/index.cjs",
6 | "module": "./dist/index.mjs",
7 | "types": "./dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "import": "./dist/index.mjs",
11 | "require": "./dist/index.cjs",
12 | "types": "./dist/index.d.ts"
13 | }
14 | },
15 | "scripts": {
16 | "build": "unbuild",
17 | "dev": "unbuild --watch",
18 | "test": "vitest run",
19 | "test:watch": "vitest",
20 | "test:coverage": "vitest run --coverage",
21 | "typecheck": "tsc --noEmit",
22 | "clean": "rm -rf dist"
23 | },
24 | "dependencies": {
25 | "better-auth": "^1.0.29",
26 | "zod": "^3.23.8"
27 | },
28 | "devDependencies": {
29 | "@refref/typescript-config": "workspace:*",
30 | "@types/node": "^20.0.0",
31 | "typescript": "^5.7.2",
32 | "unbuild": "^2.0.0",
33 | "vitest": "^2.1.8"
34 | },
35 | "peerDependencies": {
36 | "better-auth": ">=1.0.0"
37 | },
38 | "files": [
39 | "dist"
40 | ],
41 | "keywords": [
42 | "better-auth",
43 | "refref",
44 | "referral",
45 | "tracking",
46 | "analytics",
47 | "authentication"
48 | ],
49 | "license": "MIT",
50 | "author": "RefRef",
51 | "repository": {
52 | "type": "git",
53 | "url": "https://github.com/refref/refref.git",
54 | "directory": "packages/refref-better-auth"
55 | }
56 | }
--------------------------------------------------------------------------------
/apps/webapp/src/server/api/routers/product-secrets.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
3 | import { db, schema } from "@/server/db";
4 | const { product, productSecrets } = schema;
5 | import { eq, and } from "drizzle-orm";
6 | import { TRPCError } from "@trpc/server";
7 |
8 | export const productSecretsRouter = createTRPCRouter({
9 | get: protectedProcedure.input(z.string()).query(async ({ ctx, input }) => {
10 | const [secrets] = await ctx.db
11 | .select()
12 | .from(productSecrets)
13 | .where(eq(productSecrets.productId, input))
14 | .limit(1);
15 |
16 | if (!secrets) {
17 | throw new TRPCError({
18 | code: "NOT_FOUND",
19 | message: "No secrets found for this product",
20 | });
21 | }
22 |
23 | // Verify product belongs to active organization
24 | const [productRecord] = await ctx.db
25 | .select()
26 | .from(product)
27 | .where(
28 | and(
29 | eq(product.id, secrets.productId),
30 | eq(product.id, ctx.activeProductId),
31 | ),
32 | )
33 | .limit(1);
34 |
35 | if (!productRecord) {
36 | throw new TRPCError({
37 | code: "FORBIDDEN",
38 | message: "Product does not belong to your organization",
39 | });
40 | }
41 |
42 | // Return client ID and client secret
43 | return {
44 | clientId: secrets.clientId,
45 | clientSecret: secrets.clientSecret,
46 | };
47 | }),
48 | });
49 |
--------------------------------------------------------------------------------
/packages/eslint-config/next.js:
--------------------------------------------------------------------------------
1 | import js from "@eslint/js";
2 | import eslintConfigPrettier from "eslint-config-prettier";
3 | import tseslint from "typescript-eslint";
4 | import pluginReactHooks from "eslint-plugin-react-hooks";
5 | import pluginReact from "eslint-plugin-react";
6 | import globals from "globals";
7 | import pluginNext from "@next/eslint-plugin-next";
8 | import { config as baseConfig } from "./base.js";
9 |
10 | /**
11 | * A custom ESLint configuration for libraries that use Next.js.
12 | *
13 | * @type {import("eslint").Linter.Config}
14 | * */
15 | export const nextJsConfig = [
16 | ...baseConfig,
17 | js.configs.recommended,
18 | eslintConfigPrettier,
19 | ...tseslint.configs.recommended,
20 | {
21 | ...pluginReact.configs.flat.recommended,
22 | languageOptions: {
23 | ...pluginReact.configs.flat.recommended.languageOptions,
24 | globals: {
25 | ...globals.serviceworker,
26 | },
27 | },
28 | },
29 | {
30 | plugins: {
31 | "@next/next": pluginNext,
32 | },
33 | rules: {
34 | ...pluginNext.configs.recommended.rules,
35 | ...pluginNext.configs["core-web-vitals"].rules,
36 | },
37 | },
38 | {
39 | plugins: {
40 | "react-hooks": pluginReactHooks,
41 | },
42 | settings: { react: { version: "detect" } },
43 | rules: {
44 | ...pluginReactHooks.configs.recommended.rules,
45 | // React scope no longer necessary with new JSX transform.
46 | "react/react-in-jsx-scope": "off",
47 | },
48 | },
49 | ];
50 |
--------------------------------------------------------------------------------
/apps/refer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/refer",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "infisical run --env=dev -- tsx watch src/index.ts || tsx watch src/index.ts",
8 | "build": "tsc",
9 | "start": "node dist/index.js",
10 | "test": "infisical run --env=dev -- vitest run || vitest run",
11 | "test:watch": "infisical run --env=dev -- vitest || vitest",
12 | "test:ui": "infisical run --env=dev -- vitest --ui || vitest --ui",
13 | "test:run": "infisical run --env=dev -- vitest run || vitest run",
14 | "type:check": "tsc --noEmit",
15 | "lint": "eslint .",
16 | "format": "prettier --write .",
17 | "fly:deploy": "cd ../../ && flyctl deploy --app refref-refer --dockerfile Dockerfile.refer --config apps/refer/fly.toml --build-arg GIT_SHA=$(git rev-parse --short HEAD) --env GIT_SHA=$(git rev-parse --short HEAD)"
18 | },
19 | "dependencies": {
20 | "@fastify/cors": "^10.0.1",
21 | "@fastify/rate-limit": "^10.3.0",
22 | "@refref/coredb": "workspace:*",
23 | "@refref/types": "workspace:*",
24 | "@refref/utils": "workspace:*",
25 | "dotenv": "^17.2.3",
26 | "drizzle-orm": "^0.44.7",
27 | "fastify": "^5.2.0",
28 | "postgres": "^3.4.5"
29 | },
30 | "devDependencies": {
31 | "@types/node": "^22.10.2",
32 | "@vitest/ui": "^2.1.8",
33 | "pino-pretty": "^13.0.0",
34 | "playwright": "^1.49.1",
35 | "tsx": "^4.19.2",
36 | "typescript": "^5.7.2",
37 | "vitest": "^2.1.8"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/apps/e2e/.env.example:
--------------------------------------------------------------------------------
1 | # Test Environment Configuration
2 | TEST_ENV=dev # dev|staging|prod
3 | SKIP_WEBSERVER_START=true # Skip auto-starting services (use if already running)
4 | TEST_PREFIX=e2e_test_ # Prefix for all test data
5 | CLEANUP_ON_FAILURE=false # Clean up test data even on failure
6 | VIDEO_ON_FAILURE=true # Record video on test failure
7 |
8 | # Server URLs
9 | WEBAPP_URL=http://localhost:3000
10 | API_URL=http://localhost:3001
11 | REFER_URL=http://localhost:3002
12 | ACME_URL=http://localhost:3003
13 | ASSETS_URL=http://localhost:8787
14 |
15 | # Database (already set up locally, will be seeded as needed)
16 | DATABASE_URL=postgresql://postgres:postgres@localhost:5432/refref
17 |
18 | # Test User Credentials
19 | TEST_USER_EMAIL=e2e_test_admin@refref.ai
20 | TEST_USER_PASSWORD=TestPassword123!
21 |
22 | # Authentication Configuration
23 | NEXT_PUBLIC_ENABLE_PASSWORD_AUTH=true # Required for E2E tests
24 | TEST_ORGANIZATION_NAME=E2E Test Organization
25 | TEST_PRODUCT_NAME=E2E Test Product
26 | TEST_PROGRAM_NAME=E2E Test Program
27 |
28 | # Test Participants
29 | REFERRER_NAME=John Doe
30 | REFERRER_EMAIL=john.doe@example.com
31 | REFEREE_NAME=Jane Smith
32 | REFEREE_EMAIL=jane.smith@example.com
33 |
34 | # Better Auth (for test user creation)
35 | BETTER_AUTH_SECRET=test-auth-secret-for-e2e
36 | BETTER_AUTH_URL=http://localhost:3000
37 |
38 | # Safety Flags
39 | DRY_RUN=false # Run in dry-run mode (no actual changes)
40 | PRODUCTION_SAFE_MODE=true # Extra safety checks for production
41 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | postgres:
5 | image: postgres:16-alpine
6 | container_name: refref-db
7 | environment:
8 | POSTGRES_USER: postgres
9 | POSTGRES_PASSWORD: postgres
10 | POSTGRES_DB: refref
11 | ports:
12 | - "5432:5432"
13 | volumes:
14 | - postgres_data:/var/lib/postgresql/data
15 | healthcheck:
16 | test: ["CMD-SHELL", "pg_isready -U postgres"]
17 | interval: 5s
18 | timeout: 5s
19 | retries: 5
20 |
21 | webapp:
22 | build:
23 | context: .
24 | dockerfile: Dockerfile
25 | container_name: refref-webapp
26 | ports:
27 | - "3000:3000"
28 | environment:
29 | DATABASE_URL: postgresql://postgres:postgres@postgres:5432/refref
30 | BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-your-secret-key-change-in-production}
31 | BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
32 | NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3000}
33 | RESEND_API_KEY: ${RESEND_API_KEY}
34 | GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
35 | GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
36 | depends_on:
37 | postgres:
38 | condition: service_healthy
39 | volumes:
40 | - ./apps/webapp/.env:/app/apps/webapp/.env
41 | command: >
42 | sh -c "
43 | cd /app &&
44 | pnpm -F @refref/coredb db:push &&
45 | pnpm -F @refref/coredb db:seed &&
46 | cd /app/apps/webapp &&
47 | pnpm start
48 | "
49 |
50 | volumes:
51 | postgres_data:
--------------------------------------------------------------------------------
/apps/api/README.md:
--------------------------------------------------------------------------------
1 | # RefRef API
2 |
3 | Fastify API server for RefRef platform.
4 |
5 | ## Development
6 |
7 | ```bash
8 | # Install dependencies
9 | pnpm install
10 |
11 | # Run in development mode with hot reload
12 | pnpm dev
13 |
14 | # Run tests
15 | pnpm test # Run tests once
16 | pnpm test:watch # Run tests in watch mode
17 | pnpm test:ui # Run tests with UI
18 |
19 | # Type checking
20 | pnpm type:check
21 |
22 | # Build for production
23 | pnpm build
24 |
25 | # Start production server
26 | pnpm start
27 | ```
28 |
29 | ## Environment Variables
30 |
31 | ```bash
32 | PORT=3001 # Server port (default: 3001)
33 | HOST=0.0.0.0 # Server host (default: 0.0.0.0)
34 | LOG_LEVEL=info # Logging level (default: info)
35 | NODE_ENV=development
36 | ```
37 |
38 | ## Testing
39 |
40 | The API uses Vitest and Playwright for integration testing. Tests start a real server instance and make HTTP requests to validate responses.
41 |
42 | Test structure:
43 |
44 | - `test/utils/testServer.ts` - Test server utilities for starting/stopping the server
45 | - `test/health.test.ts` - Health endpoint tests
46 |
47 | Each test suite:
48 |
49 | 1. Starts a test server on a random port
50 | 2. Creates a Playwright API request context
51 | 3. Runs tests against real endpoints
52 | 4. Cleans up resources after tests complete
53 |
54 | ## Endpoints
55 |
56 | - `GET /` - Root health check endpoint
57 | - Returns: `{ok: true, service: "refref-api"}`
58 | - `GET /health` - Health check endpoint
59 | - Returns: `{ok: true, service: "refref-api"}`
60 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/preview-pane.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type * as React from "react";
4 | import { XIcon } from "lucide-react";
5 |
6 | import { cn } from "@/lib/utils";
7 | import { Button } from "@refref/ui/components/button";
8 | import { ScrollArea } from "@refref/ui/components/scroll-area";
9 | import { Separator } from "@refref/ui/components/separator";
10 |
11 | interface PreviewPaneProps extends React.HTMLAttributes {
12 | isOpen?: boolean;
13 | onClose?: () => void;
14 | children?: React.ReactNode;
15 | }
16 |
17 | export function PreviewPane({
18 | className,
19 | isOpen = true,
20 | onClose,
21 | children,
22 | ...props
23 | }: PreviewPaneProps) {
24 | if (!isOpen) return null;
25 |
26 | return (
27 |
34 | {/*
35 |
Preview
36 | {onClose && (
37 |
46 | )}
47 | */}
48 |
49 | {children}
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/packages/coredb/src/index.ts:
--------------------------------------------------------------------------------
1 | import { drizzle } from "drizzle-orm/postgres-js";
2 | import postgres from "postgres";
3 | import * as schema from "./schema.js";
4 |
5 | /**
6 | * Cache the database connections in development. This avoids creating a new connection on every HMR
7 | * update.
8 | */
9 | const globalForDb = globalThis as unknown as {
10 | connections: Map;
11 | };
12 |
13 | if (!globalForDb.connections) {
14 | globalForDb.connections = new Map();
15 | }
16 |
17 | /**
18 | * Creates a database connection with the provided URL
19 | * @param databaseUrl - The PostgreSQL connection string
20 | * @returns Drizzle database instance
21 | */
22 | export function createDb(databaseUrl: string) {
23 | const isDev = process.env.NODE_ENV !== "production";
24 |
25 | let conn: postgres.Sql;
26 |
27 | if (isDev) {
28 | // In development, cache connections by URL
29 | const cachedConn = globalForDb.connections.get(databaseUrl);
30 | if (cachedConn) {
31 | conn = cachedConn;
32 | } else {
33 | conn = postgres(databaseUrl);
34 | globalForDb.connections.set(databaseUrl, conn);
35 | }
36 | } else {
37 | // In production, create new connection
38 | conn = postgres(databaseUrl);
39 | }
40 |
41 | return drizzle(conn, { schema });
42 | }
43 |
44 | export type DBType = ReturnType;
45 |
46 | // Export schema for direct imports
47 | export * as schema from "./schema.js";
48 |
49 | // Export organization-level service account utilities
50 | export * from "./lib/organization-service-account.js";
51 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | node_modules/
3 | **/node_modules
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 |
9 | # Build outputs
10 | .next/
11 | **/.next
12 | dist/
13 | **/dist
14 | build/
15 | out/
16 |
17 | # Environment files
18 | .env
19 | .env.*
20 | **/.env*
21 | .infisical.json
22 |
23 | # IDE and editor files
24 | .vscode/
25 | .idea/
26 | *.swp
27 | *.swo
28 | *~
29 |
30 | # OS generated files
31 | .DS_Store
32 | .DS_Store?
33 | **/.DS_Store
34 | ._*
35 | .Spotlight-V100
36 | .Trashes
37 | ehthumbs.db
38 | Thumbs.db
39 |
40 | # Git
41 | .git/
42 | .gitignore
43 | .gitattributes
44 |
45 | # Docker
46 | Dockerfile*
47 | .dockerignore
48 |
49 | # Testing
50 | coverage/
51 | .nyc_output/
52 | *.lcov
53 | *.test.*
54 | *.spec.*
55 | **/__tests__
56 |
57 | # Logs
58 | logs/
59 | *.log
60 |
61 | # Runtime data
62 | pids/
63 | *.pid
64 | *.seed
65 | *.pid.lock
66 |
67 | # Turbo
68 | .turbo/
69 | **/.turbo
70 |
71 | # Temporary files
72 | tmp/
73 | temp/
74 |
75 | # Cache directories
76 | .cache/
77 | .parcel-cache/
78 | .eslintcache
79 |
80 | # Optional npm cache directory
81 | .npm
82 |
83 | # Yarn Integrity file
84 | .yarn-integrity
85 |
86 | # Output of 'npm pack'
87 | *.tgz
88 | *.tar.gz
89 |
90 | # Documentation (top-level only)
91 | README.md
92 | /docs/
93 |
94 | # Fly.io
95 | fly.toml
96 | **/fly.toml
97 |
98 | # CI/CD
99 | .github/
100 |
101 | # Apps not needed for api/refer builds
102 | apps/www
103 | apps/assets
104 |
105 | # Infisical
106 | .infisical/
--------------------------------------------------------------------------------
/packages/coredb/drizzle/0001_flaky_madelyne_pryor.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE "reflink" (
2 | "id" text PRIMARY KEY NOT NULL,
3 | "created_at" timestamp DEFAULT now() NOT NULL,
4 | "updated_at" timestamp DEFAULT now() NOT NULL,
5 | "slug" text NOT NULL,
6 | "refcode_id" text NOT NULL,
7 | "product_id" text NOT NULL
8 | );
9 | --> statement-breakpoint
10 | DROP INDEX "refcode_global_code_unique_idx";--> statement-breakpoint
11 | DROP INDEX "refcode_local_code_unique_idx";--> statement-breakpoint
12 | DROP INDEX "refcode_code_idx";--> statement-breakpoint
13 | ALTER TABLE "reflink" ADD CONSTRAINT "reflink_refcode_id_refcode_id_fk" FOREIGN KEY ("refcode_id") REFERENCES "public"."refcode"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
14 | ALTER TABLE "reflink" ADD CONSTRAINT "reflink_product_id_product_id_fk" FOREIGN KEY ("product_id") REFERENCES "public"."product"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
15 | CREATE UNIQUE INDEX "reflink_slug_product_unique_idx" ON "reflink" USING btree ("slug","product_id");--> statement-breakpoint
16 | CREATE INDEX "reflink_slug_idx" ON "reflink" USING btree ("slug");--> statement-breakpoint
17 | CREATE INDEX "reflink_refcode_id_idx" ON "reflink" USING btree ("refcode_id");--> statement-breakpoint
18 | CREATE INDEX "reflink_product_id_idx" ON "reflink" USING btree ("product_id");--> statement-breakpoint
19 | CREATE UNIQUE INDEX "refcode_code_unique_idx" ON "refcode" USING btree ("code");--> statement-breakpoint
20 | CREATE INDEX "refcode_product_id_idx" ON "refcode" USING btree ("product_id");--> statement-breakpoint
21 | ALTER TABLE "refcode" DROP COLUMN "global";
--------------------------------------------------------------------------------
/packages/attribution-script/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/attribution-script",
3 | "version": "0.1.0",
4 | "description": "Attribution script for tracking referral sources and managing attribution data",
5 | "type": "module",
6 | "main": "./dist/attribution-script.umd.js",
7 | "module": "./dist/attribution-script.es.js",
8 | "types": "./dist/types/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./dist/attribution-script.es.js",
12 | "require": "./dist/attribution-script.umd.js",
13 | "types": "./dist/types/index.d.ts"
14 | },
15 | "./dist/*": "./dist/*"
16 | },
17 | "files": [
18 | "dist"
19 | ],
20 | "scripts": {
21 | "dev": "vite --port 5174",
22 | "build": "infisical run --env=dev -- vite build --mode development || vite build --mode development",
23 | "build:prod": "infisical run --env=prod -- vite build --mode production || vite build --mode production",
24 | "clean": "rm -rf dist src/**/*.js src/**/*.js.map",
25 | "lint": "eslint . --report-unused-disable-directives",
26 | "test": "vitest run",
27 | "test:watch": "vitest",
28 | "preview": "vite preview",
29 | "type:check": "tsc --noEmit"
30 | },
31 | "keywords": [
32 | "attribution",
33 | "referral",
34 | "tracking",
35 | "analytics"
36 | ],
37 | "author": "",
38 | "devDependencies": {
39 | "@refref/eslint-config": "workspace:*",
40 | "@refref/typescript-config": "workspace:*",
41 | "jsdom": "^25.0.0",
42 | "typescript": "^5.9.2",
43 | "vite": "^6.3.3",
44 | "vite-plugin-dts": "^3.7.0",
45 | "vitest": "^3.1.4"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/apps/webapp/src/server/api/routers/referral.ts:
--------------------------------------------------------------------------------
1 | import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
2 | import { SignJWT } from "jose";
3 | import { env } from "@/env";
4 | import type { JwtPayloadType } from "@refref/types";
5 | import { eq } from "drizzle-orm";
6 | import { db, schema } from "@/server/db";
7 | import { TRPCError } from "@trpc/server";
8 | const { user: userTable } = schema;
9 |
10 | export const referralRouter = createTRPCRouter({
11 | getWidgetToken: protectedProcedure.query(async ({ ctx }) => {
12 | // Check if referral credentials are configured
13 | if (
14 | !env.REFERRAL_PROGRAM_CLIENT_SECRET ||
15 | !env.REFERRAL_PROGRAM_CLIENT_ID ||
16 | !env.NEXT_PUBLIC_REFREF_PRODUCT_ID
17 | ) {
18 | throw new TRPCError({
19 | code: "PRECONDITION_FAILED",
20 | message: "Referral program credentials are not configured",
21 | });
22 | }
23 |
24 | const user = await db.query.user.findFirst({
25 | where: eq(userTable.id, ctx.userId),
26 | });
27 |
28 | const payload: JwtPayloadType = {
29 | sub: ctx.userId,
30 | email: user!.email,
31 | name: user!.name,
32 | productId: env.NEXT_PUBLIC_REFREF_PRODUCT_ID,
33 | };
34 |
35 | const token = await new SignJWT(payload)
36 | .setProtectedHeader({ alg: "HS256" })
37 | .setIssuedAt()
38 | .setExpirationTime("1h")
39 | .sign(new TextEncoder().encode(env.REFERRAL_PROGRAM_CLIENT_SECRET));
40 |
41 | return {
42 | token,
43 | clientId: env.REFERRAL_PROGRAM_CLIENT_ID,
44 | userId: ctx.userId,
45 | };
46 | }),
47 | });
48 |
--------------------------------------------------------------------------------
/apps/www/app/docs/[...slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { source, openapi } from "@/lib/source";
2 | import {
3 | DocsPage,
4 | DocsBody,
5 | DocsDescription,
6 | DocsTitle,
7 | } from "fumadocs-ui/page";
8 | import { notFound } from "next/navigation";
9 | import defaultMdxComponents from "fumadocs-ui/mdx";
10 |
11 | export default async function Page(props: {
12 | params: Promise<{ slug?: string[] }>;
13 | }) {
14 | const params = await props.params;
15 | const page = source.getPage(params.slug);
16 | if (!page) notFound();
17 |
18 | const MDX = page.data.body;
19 |
20 | return (
21 |
34 | {page.data.title}
35 | {page.data.description}
36 |
37 |
40 |
41 |
42 | );
43 | }
44 |
45 | export async function generateStaticParams() {
46 | return source.generateParams();
47 | }
48 |
49 | export async function generateMetadata(props: {
50 | params: Promise<{ slug?: string[] }>;
51 | }) {
52 | const params = await props.params;
53 | const page = source.getPage(params.slug);
54 | if (!page) notFound();
55 |
56 | return {
57 | title: page.data.title,
58 | description: page.data.description,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/packages/types/src/program-template-steps.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 | import { currencySchema } from "./program-config";
3 |
4 | // New simplified reward step schema
5 | export const programTemplateRewardStepSchemaV2 = z.object({
6 | referrerReward: z.object({
7 | enabled: z.boolean(),
8 | valueType: z.enum(["fixed", "percentage"]),
9 | value: z.number().positive(),
10 | currency: currencySchema,
11 | }),
12 | refereeReward: z.object({
13 | enabled: z.boolean(),
14 | valueType: z.enum(["fixed", "percentage"]),
15 | value: z.number().positive(),
16 | currency: currencySchema.optional(), // Only for fixed discounts
17 | minPurchaseAmount: z.number().positive().optional(),
18 | validityDays: z.number().int().positive().optional(),
19 | }),
20 | });
21 |
22 | export type ProgramTemplateRewardStepV2Type = z.infer<
23 | typeof programTemplateRewardStepSchemaV2
24 | >;
25 |
26 | // Keep old schema for backward compatibility
27 | /** @deprecated Use programTemplateRewardStepSchemaV2 instead */
28 | export const programTemplateRewardStepSchema = z.object({
29 | type: z.literal("cash"),
30 | defaultCurrency: z.string().min(1),
31 | rewardType: z.enum(["percentage", "fixed"]),
32 | revenueSharePercentage: z.number().min(0).max(50),
33 | revenueSharePeriodType: z.literal("lifetime"),
34 | cap: z.number().min(0),
35 | newUserRewardType: z.enum(["percentage", "fixed"]),
36 | newUserRewardMonths: z.number().min(1),
37 | newUserRewardValue: z.number().min(0),
38 | });
39 |
40 | /** @deprecated Use ProgramTemplateRewardStepV2Type instead */
41 | export type ProgramTemplateRewardStepType = z.infer<
42 | typeof programTemplateRewardStepSchema
43 | >;
44 |
--------------------------------------------------------------------------------
/packages/ui/src/components/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
5 | import { CircleIcon } from "lucide-react";
6 |
7 | import { cn } from "@refref/ui/lib/utils";
8 |
9 | function RadioGroup({
10 | className,
11 | ...props
12 | }: React.ComponentProps) {
13 | return (
14 |
19 | );
20 | }
21 |
22 | function RadioGroupItem({
23 | className,
24 | ...props
25 | }: React.ComponentProps) {
26 | return (
27 |
35 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export { RadioGroup, RadioGroupItem };
46 |
--------------------------------------------------------------------------------
/apps/www/content/docs/platform/setup-affiliate-program.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Affiliate Program Setup
3 | description: Learn how to configure and launch an affiliate program with RefRef
4 | ---
5 | RefRef makes it easy to create and manage affiliate programs that reward partners for driving traffic and conversions to your business.
6 |
7 | ## What is an Affiliate Program?
8 |
9 | An affiliate program is a partnership where external individuals or businesses (affiliates) promote your products or services in exchange for a commission on successful referrals or sales.
10 |
11 | ## Getting Started
12 |
13 | To set up your affiliate program in RefRef:
14 |
15 | 1. **Navigate to the Affiliate Dashboard** in your RefRef admin panel
16 | 2. **Configure Commission Structure** - Set commission rates, payment thresholds, and tracking periods
17 | 3. **Customize Affiliate Portal** - Brand your affiliate dashboard and marketing materials
18 | 4. **Set Up Tracking Links** - Create unique tracking links for your affiliates
19 | 5. **Define Terms & Conditions** - Establish clear guidelines for your affiliate relationships
20 |
21 | ## Advanced Configuration
22 |
23 | - **Multi-tier Commissions** - Create cascading commission structures for affiliate networks
24 | - **Custom Conversion Rules** - Define specific actions that trigger commission payouts
25 | - **Fraud Prevention** - Implement safeguards against fraudulent affiliate activities
26 |
27 | ## Integrating with Your Systems
28 |
29 | RefRef's affiliate tracking can be integrated with your:
30 | - Payment processors
31 | - CRM systems
32 | - Marketing automation tools
33 | - Analytics platforms
34 |
35 | For detailed implementation guides, see our [API documentation](/docs/api).
--------------------------------------------------------------------------------
/apps/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/api",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "scripts": {
7 | "dev": "infisical run --env=dev -- tsx watch src/index.ts || tsx watch src/index.ts",
8 | "build": "tsc",
9 | "start": "node dist/index.js",
10 | "test": "infisical run --env=dev -- vitest run || vitest run",
11 | "test:watch": "infisical run --env=dev -- vitest || vitest",
12 | "test:ui": "infisical run --env=dev -- vitest --ui || vitest --ui",
13 | "test:run": "infisical run --env=dev -- vitest run || vitest run",
14 | "type:check": "tsc --noEmit",
15 | "lint": "eslint .",
16 | "format": "prettier --write .",
17 | "fly:deploy": "cd ../../ && flyctl deploy --app refref-api --dockerfile Dockerfile.api --config apps/api/fly.toml --build-arg GIT_SHA=$(git rev-parse --short HEAD) --env GIT_SHA=$(git rev-parse --short HEAD)"
18 | },
19 | "dependencies": {
20 | "@fastify/cors": "^10.0.1",
21 | "@paralleldrive/cuid2": "^2.2.2",
22 | "@refref/auth": "workspace:*",
23 | "@refref/coredb": "workspace:*",
24 | "@refref/id": "workspace:*",
25 | "@refref/types": "workspace:*",
26 | "@refref/utils": "workspace:*",
27 | "@tsndr/cloudflare-worker-jwt": "^3.2.0",
28 | "dotenv": "^17.2.3",
29 | "drizzle-orm": "^0.44.7",
30 | "fastify": "^5.2.0",
31 | "fastify-plugin": "^5.0.1",
32 | "jose": "^5.9.6",
33 | "postgres": "^3.4.5",
34 | "zod": "^3.23.8"
35 | },
36 | "devDependencies": {
37 | "@types/node": "^22.10.2",
38 | "@vitest/ui": "^2.1.8",
39 | "pino-pretty": "^13.0.0",
40 | "playwright": "^1.49.1",
41 | "tsx": "^4.19.2",
42 | "typescript": "^5.7.2",
43 | "vitest": "^2.1.8"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/apps/webapp/src/components/referral-widget-preview.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ReferralWidgetContent } from "@refref/ui/components/referral-widget/referral-widget-dialog-content";
4 |
5 | export default function ReferralWidgetPreview() {
6 | const referralLink = "acme.refref.ai/th5tdf";
7 |
8 | // Color values from screenshot
9 | const containerBgColor = "#23263a";
10 | const containerTextColor = "#fff";
11 | const primaryColor = "#a259ff"; // Purple button
12 |
13 | // Social channels in the order of the screenshot
14 | const enabledChannels: ("x" | "linkedin" | "email" | "share")[] = [
15 | "x",
16 | "linkedin",
17 | "email",
18 | "share",
19 | ];
20 |
21 | const widgetConfig = {
22 | position: "bottom-right" as const,
23 | triggerText: "Refer & Earn",
24 | buttonBgColor: primaryColor,
25 | buttonTextColor: containerTextColor,
26 | icon: "gift" as const,
27 | title: "Refer friends, get rewards",
28 | subtitle: "Earn rewards for each referral",
29 | logoUrl: "",
30 | modalBgColor: containerBgColor,
31 | accentColor: primaryColor,
32 | textColor: containerTextColor,
33 | shareMessage: "Join me and get a reward!",
34 | enabledPlatforms: {
35 | facebook: false,
36 | twitter: enabledChannels.includes("x"),
37 | linkedin: enabledChannels.includes("linkedin"),
38 | whatsapp: false,
39 | email: enabledChannels.includes("email"),
40 | instagram: false,
41 | telegram: false,
42 | },
43 | referralLink: referralLink,
44 | productName: "YourSaaS",
45 | };
46 |
47 | return (
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/packages/ui/src/components/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card";
5 |
6 | import { cn } from "@refref/ui/lib/utils";
7 |
8 | function HoverCard({
9 | ...props
10 | }: React.ComponentProps) {
11 | return ;
12 | }
13 |
14 | function HoverCardTrigger({
15 | ...props
16 | }: React.ComponentProps) {
17 | return (
18 |
19 | );
20 | }
21 |
22 | function HoverCardContent({
23 | className,
24 | align = "center",
25 | sideOffset = 4,
26 | ...props
27 | }: React.ComponentProps) {
28 | return (
29 |
30 |
40 |
41 | );
42 | }
43 |
44 | export { HoverCard, HoverCardTrigger, HoverCardContent };
45 |
--------------------------------------------------------------------------------
/apps/api/src/routes/v1/programs.ts:
--------------------------------------------------------------------------------
1 | import { FastifyInstance } from "fastify";
2 | import { z } from "zod";
3 |
4 | interface GetProgramParams {
5 | Params: {
6 | id: string;
7 | };
8 | }
9 |
10 | export default async function programsRoutes(fastify: FastifyInstance) {
11 | /**
12 | * GET /v1/programs/:id
13 | * Get program configuration by ID with API key authentication
14 | */
15 | fastify.get(
16 | "/:id",
17 | {
18 | preHandler: [fastify.authenticateApiKey],
19 | },
20 | async (request, reply) => {
21 | try {
22 | const { id: programId } = request.params;
23 |
24 | // Fetch the program from database
25 | const programRecord = await request.db.query.program.findFirst({
26 | where: (program, { eq }) => eq(program.id, programId),
27 | });
28 |
29 | if (!programRecord) {
30 | return reply.code(404).send({
31 | error: "Not Found",
32 | message: "Program not found",
33 | });
34 | }
35 |
36 | // Return the program data
37 | return reply.send({
38 | success: true,
39 | data: programRecord,
40 | });
41 | } catch (error) {
42 | request.log.error({ error }, "Error fetching program");
43 |
44 | if (error instanceof z.ZodError) {
45 | return reply.code(400).send({
46 | error: "Bad Request",
47 | message: "Invalid request parameters",
48 | details: error.issues,
49 | });
50 | }
51 |
52 | return reply.code(500).send({
53 | error: "Internal Server Error",
54 | message: "An unexpected error occurred",
55 | });
56 | }
57 | },
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/apps/www/README.md:
--------------------------------------------------------------------------------
1 | # www
2 |
3 | This is a Next.js application generated with
4 | [Create Fumadocs](https://github.com/fuma-nama/fumadocs).
5 |
6 | Run development server:
7 |
8 | ```bash
9 | npm run dev
10 | # or
11 | pnpm dev
12 | # or
13 | yarn dev
14 | ```
15 |
16 | Open http://localhost:3000 with your browser to see the result.
17 |
18 | ## Content Management
19 |
20 | ### Fetching Blog Content
21 |
22 | This project includes a script to fetch blog content and images from an S3-compatible storage (Wasabi):
23 |
24 | ```bash
25 | # Set up environment variables (see .env.example)
26 | pnpm fetch-content
27 | ```
28 |
29 | The script will:
30 |
31 | - Fetch MDX files from the `blog/` folder in the S3 bucket and save them to `content/blogs/`
32 | - Fetch images from the `blog-images/` folder in the S3 bucket and save them to `public/blog/`
33 |
34 | For more details, see the [scripts README](./scripts/README.md).
35 |
36 | ### Building the Application
37 |
38 | The build process includes fetching content from S3:
39 |
40 | ```bash
41 | pnpm build
42 | ```
43 |
44 | The build will fail if the content fetch fails. This ensures that the site is always built with the latest content and that any issues with the content fetch process are immediately apparent.
45 |
46 | To use this in CI/CD environments, make sure to configure the appropriate AWS credentials as environment variables.
47 |
48 | ## Learn More
49 |
50 | To learn more about Next.js and Fumadocs, take a look at the following
51 | resources:
52 |
53 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
54 | features and API.
55 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
56 | - [Fumadocs](https://fumadocs.vercel.app) - learn about Fumadocs
57 |
--------------------------------------------------------------------------------
/packages/coredb/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@refref/coredb",
3 | "version": "0.1.0",
4 | "private": true,
5 | "type": "module",
6 | "main": "./dist/index.js",
7 | "types": "./dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "types": "./dist/index.d.ts",
11 | "import": "./dist/index.js"
12 | },
13 | "./schema": {
14 | "types": "./dist/schema.d.ts",
15 | "import": "./dist/schema.js"
16 | }
17 | },
18 | "scripts": {
19 | "db:generate": "infisical run --env=dev -- drizzle-kit generate || drizzle-kit generate",
20 | "db:migrate": "infisical run --env=dev -- drizzle-kit migrate || drizzle-kit migrate",
21 | "db:push": "infisical run --env=dev -- drizzle-kit push || drizzle-kit push",
22 | "db:studio": "infisical run --env=dev -- drizzle-kit studio || drizzle-kit studio",
23 | "db:seed": "infisical run --env=dev -- tsx src/seed.ts seed || tsx src/seed.ts seed",
24 | "db:deleteseed": "infisical run --env=dev -- tsx src/seed.ts delete || tsx src/seed.ts delete",
25 | "migrate:org-api-keys": "infisical run --env=dev -- tsx scripts/migrate-to-org-api-keys.ts || tsx scripts/migrate-to-org-api-keys.ts",
26 | "lint": "eslint .",
27 | "lint:fix": "eslint . --fix",
28 | "build": "tsc",
29 | "type:check": "tsc --noEmit"
30 | },
31 | "dependencies": {
32 | "@paralleldrive/cuid2": "^2.2.2",
33 | "@refref/id": "workspace:*",
34 | "@refref/types": "workspace:*",
35 | "drizzle-orm": "^0.44.7",
36 | "postgres": "^3.4.5",
37 | "zod": "^4.0.17"
38 | },
39 | "devDependencies": {
40 | "@refref/typescript-config": "workspace:*",
41 | "@types/node": "^24.3.0",
42 | "drizzle-kit": "^0.31.6",
43 | "tsx": "^4.19.2",
44 | "typescript": "^5.9.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------