├── .npmrc
├── apps
├── basejump-docs
│ ├── src
│ │ ├── pages
│ │ │ ├── docs
│ │ │ │ ├── fetching-data.mdx
│ │ │ │ ├── react
│ │ │ │ │ ├── invite-member-form.mdx
│ │ │ │ │ ├── accept-invitation-form.mdx
│ │ │ │ │ └── user-session.mdx
│ │ │ │ ├── billing-new.mdx
│ │ │ │ ├── index.mdx
│ │ │ │ ├── deployment.mdx
│ │ │ │ ├── understanding-accounts.mdx
│ │ │ │ └── testing.mdx
│ │ │ ├── 404.jsx
│ │ │ ├── blog
│ │ │ │ ├── hello-world.mdx
│ │ │ │ └── index.jsx
│ │ │ ├── _document.jsx
│ │ │ ├── index.jsx
│ │ │ ├── api
│ │ │ │ └── og.jsx
│ │ │ └── _app.jsx
│ │ ├── mdx
│ │ │ ├── remark.mjs
│ │ │ └── recma.mjs
│ │ ├── components
│ │ │ ├── react
│ │ │ │ └── HookInstallation.mdx
│ │ │ ├── Prose.jsx
│ │ │ ├── ContentFooter.jsx
│ │ │ ├── icons
│ │ │ │ ├── BoltIcon.jsx
│ │ │ │ ├── ShirtIcon.jsx
│ │ │ │ ├── LinkIcon.jsx
│ │ │ │ ├── MagnifyingGlassIcon.jsx
│ │ │ │ ├── CheckIcon.jsx
│ │ │ │ ├── BookIcon.jsx
│ │ │ │ ├── PaperAirplaneIcon.jsx
│ │ │ │ ├── DocumentIcon.jsx
│ │ │ │ ├── FaceSmileIcon.jsx
│ │ │ │ ├── ListIcon.jsx
│ │ │ │ ├── PaperClipIcon.jsx
│ │ │ │ ├── PackageIcon.jsx
│ │ │ │ ├── ChevronRightLeftIcon.jsx
│ │ │ │ ├── CopyIcon.jsx
│ │ │ │ ├── EnvelopeIcon.jsx
│ │ │ │ ├── BellIcon.jsx
│ │ │ │ ├── CartIcon.jsx
│ │ │ │ ├── ChatBubbleIcon.jsx
│ │ │ │ ├── ShapesIcon.jsx
│ │ │ │ ├── SquaresPlusIcon.jsx
│ │ │ │ ├── ClipboardIcon.jsx
│ │ │ │ ├── MapPinIcon.jsx
│ │ │ │ ├── CalendarIcon.jsx
│ │ │ │ ├── FolderIcon.jsx
│ │ │ │ ├── UsersIcon.jsx
│ │ │ │ ├── UserIcon.jsx
│ │ │ │ ├── TagIcon.jsx
│ │ │ │ └── CogIcon.jsx
│ │ │ ├── SocialLink.jsx
│ │ │ ├── Logo.jsx
│ │ │ ├── homepage
│ │ │ │ ├── SupabaseExamples.mdx
│ │ │ │ ├── CodeExamples.mdx
│ │ │ │ ├── CrossPlatform.jsx
│ │ │ │ ├── Features.jsx
│ │ │ │ ├── UiComponents.jsx
│ │ │ │ └── BuildWithSupabase.jsx
│ │ │ ├── GridPattern.jsx
│ │ │ ├── HeroPattern.jsx
│ │ │ ├── Layout.jsx
│ │ │ ├── BlogLayout.jsx
│ │ │ ├── Icons.jsx
│ │ │ ├── Tag.jsx
│ │ │ ├── Guides.jsx
│ │ │ ├── ContentLayout.jsx
│ │ │ ├── ModeToggle.jsx
│ │ │ ├── Button.jsx
│ │ │ ├── ContentHeader.jsx
│ │ │ ├── Header.jsx
│ │ │ └── Heading.jsx
│ │ ├── lib
│ │ │ ├── remToPx.js
│ │ │ ├── get-full-url.js
│ │ │ └── get-navigation.js
│ │ ├── images
│ │ │ └── logos
│ │ │ │ ├── ruby.svg
│ │ │ │ ├── python.svg
│ │ │ │ ├── javascript.svg
│ │ │ │ ├── node.svg
│ │ │ │ ├── php.svg
│ │ │ │ ├── go.svg
│ │ │ │ ├── python-logo.svg
│ │ │ │ ├── swift.svg
│ │ │ │ └── typescript.svg
│ │ └── styles
│ │ │ └── tailwind.css
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ ├── basejump-logo.png
│ │ │ ├── react-components.png
│ │ │ └── docs
│ │ │ ├── copy-template.png
│ │ │ ├── stripe-products.png
│ │ │ ├── stripe-tokens.png
│ │ │ ├── stripe-webhooks.png
│ │ │ ├── supabase-info.png
│ │ │ ├── new-supabase-project.png
│ │ │ ├── react
│ │ │ ├── new-team-form.png
│ │ │ ├── account-selector.png
│ │ │ └── user-account-button.png
│ │ │ ├── supabase-start-results.png
│ │ │ ├── disable-email-verification.png
│ │ │ └── deploying-vercel
│ │ │ ├── vercel-github-link.png
│ │ │ ├── vercel-env-variables.png
│ │ │ ├── vercel-github-link-2.png
│ │ │ └── create-vercel-project.png
│ ├── jsconfig.json
│ ├── prettier.config.js
│ ├── postcss.config.js
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── next.config.mjs
│ ├── tailwind.config.js
│ └── package.json
└── nextjs-starter
│ ├── supabase
│ ├── .gitignore
│ ├── functions
│ │ ├── .env.example
│ │ ├── billing-webhooks
│ │ │ └── index.ts
│ │ └── billing-functions
│ │ │ └── index.ts
│ └── migrations
│ │ └── 20231208070356_install-basejump.sql
│ ├── src
│ ├── app
│ │ ├── favicon.ico
│ │ ├── opengraph-image.png
│ │ ├── twitter-image.png
│ │ ├── dashboard
│ │ │ ├── [accountSlug]
│ │ │ │ ├── page.tsx
│ │ │ │ ├── settings
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ ├── billing
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── members
│ │ │ │ │ │ └── page.tsx
│ │ │ │ └── layout.tsx
│ │ │ ├── (personalAccount)
│ │ │ │ ├── page.tsx
│ │ │ │ ├── settings
│ │ │ │ │ ├── page.tsx
│ │ │ │ │ ├── layout.tsx
│ │ │ │ │ ├── teams
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── billing
│ │ │ │ │ │ └── page.tsx
│ │ │ │ └── layout.tsx
│ │ │ ├── NavigatingAccountSelector.tsx
│ │ │ └── DashboardHeader.tsx
│ │ ├── layout.tsx
│ │ ├── auth
│ │ │ └── callback
│ │ │ │ └── route.ts
│ │ ├── page.tsx
│ │ └── globals.css
│ ├── lib
│ │ └── utils.ts
│ ├── utils
│ │ └── supabase
│ │ │ ├── client.ts
│ │ │ ├── server.ts
│ │ │ └── middleware.ts
│ ├── components
│ │ ├── dashboard
│ │ │ ├── DashboardTitle.tsx
│ │ │ └── SettingsNavigation.tsx
│ │ ├── Logo.tsx
│ │ ├── AuthenticatedNavigation.tsx
│ │ ├── Step.tsx
│ │ ├── ui
│ │ │ ├── label.tsx
│ │ │ ├── separator.tsx
│ │ │ ├── input.tsx
│ │ │ ├── popover.tsx
│ │ │ ├── alert.tsx
│ │ │ ├── button.tsx
│ │ │ └── card.tsx
│ │ ├── AuthButton.tsx
│ │ ├── BasejumpLogo.tsx
│ │ ├── Code.tsx
│ │ ├── Header.tsx
│ │ └── basejump
│ │ │ └── UserAccountButton.tsx
│ └── middleware.ts
│ ├── next.config.js
│ ├── postcss.config.js
│ ├── public
│ └── images
│ │ └── basejump-logo.png
│ ├── .env.local.example
│ ├── components.json
│ ├── .gitignore
│ ├── tsconfig.json
│ ├── package.json
│ └── tailwind.config.js
├── packages
├── shared
│ ├── src
│ │ ├── index.ts
│ │ └── types
│ │ │ ├── index.ts
│ │ │ └── api.ts
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ └── package.json
├── next
│ ├── tsconfig.json
│ ├── src
│ │ ├── utils
│ │ │ └── create-client.ts
│ │ ├── hooks
│ │ │ ├── use-accounts.ts
│ │ │ ├── use-account.ts
│ │ │ ├── use-personal-account.ts
│ │ │ ├── use-account-by-slug.ts
│ │ │ ├── use-billing-plans.ts
│ │ │ ├── use-account-members.ts
│ │ │ ├── use-account-invitations.ts
│ │ │ ├── use-account-billing-status.ts
│ │ │ └── use-account-invitation-lookup.ts
│ │ ├── components
│ │ │ └── basejump-user-session.tsx
│ │ └── index.tsx
│ ├── tsup.config.ts
│ ├── README.md
│ └── package.json
├── react
│ ├── tsconfig.json
│ ├── tsup.config.ts
│ ├── README.md
│ ├── src
│ │ ├── components
│ │ │ ├── signed-out.tsx
│ │ │ └── signed-in.tsx
│ │ ├── hooks
│ │ │ ├── use-accounts.ts
│ │ │ ├── use-personal-account.ts
│ │ │ ├── use-account.ts
│ │ │ ├── use-account-by-slug.ts
│ │ │ ├── use-account-members.ts
│ │ │ ├── use-account-invitation-lookup.ts
│ │ │ ├── use-account-invitations.ts
│ │ │ ├── use-account-billing-status.ts
│ │ │ └── use-billing-plans.ts
│ │ └── index.tsx
│ └── package.json
├── tsconfig
│ ├── package.json
│ ├── library.json
│ ├── react-library.json
│ ├── base.json
│ └── nextjs.json
└── eslint-config-custom
│ ├── index.js
│ └── package.json
├── .eslintrc.js
├── package.json
├── turbo.json
├── .gitignore
├── README.md
└── LICENSE.md
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers = true
2 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/fetching-data.mdx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./types";
--------------------------------------------------------------------------------
/apps/nextjs-starter/supabase/.gitignore:
--------------------------------------------------------------------------------
1 | # Supabase
2 | .branches
3 | .temp
4 | .env
5 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/favicon.ico
--------------------------------------------------------------------------------
/packages/shared/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./api";
2 | export type {Database as BASEJUMP_DATABASE} from "./basejump-types";
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/nextjs-starter/src/app/favicon.ico
--------------------------------------------------------------------------------
/apps/nextjs-starter/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/nextjs-starter/src/app/opengraph-image.png
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/nextjs-starter/src/app/twitter-image.png
--------------------------------------------------------------------------------
/apps/basejump-docs/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/basejump-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/basejump-logo.png
--------------------------------------------------------------------------------
/apps/nextjs-starter/public/images/basejump-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/nextjs-starter/public/images/basejump-logo.png
--------------------------------------------------------------------------------
/apps/basejump-docs/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | singleQuote: true,
3 | semi: false,
4 | plugins: [require('prettier-plugin-tailwindcss')],
5 | }
6 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/react-components.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/react-components.png
--------------------------------------------------------------------------------
/packages/next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": ["./src"],
4 | "exclude": ["dist", "build", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": ["./src"],
4 | "exclude": ["dist", "build", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/copy-template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/copy-template.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/stripe-products.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/stripe-products.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/stripe-tokens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/stripe-tokens.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/stripe-webhooks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/stripe-webhooks.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/supabase-info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/supabase-info.png
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/page.tsx:
--------------------------------------------------------------------------------
1 | export default function PersonalAccountPage() {
2 | return (
3 | <>Team Account>
4 | )
5 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/page.tsx:
--------------------------------------------------------------------------------
1 | export default function PersonalAccountPage() {
2 | return (
3 | <>Personal Account>
4 | )
5 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/new-supabase-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/new-supabase-project.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/react/new-team-form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/react/new-team-form.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/react/account-selector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/react/account-selector.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/supabase-start-results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/supabase-start-results.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/react/user-account-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/react/user-account-button.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/disable-email-verification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/disable-email-verification.png
--------------------------------------------------------------------------------
/apps/nextjs-starter/supabase/functions/.env.example:
--------------------------------------------------------------------------------
1 | STRIPE_API_KEY=sk_test_asdf
2 | STRIPE_WEBHOOK_SIGNING_SECRET=whsec_asdf
3 | STRIPE_DEFAULT_PLAN_ID=price_asdf
4 | STRIPE_DEFAULT_TRIAL_DAYS=30
--------------------------------------------------------------------------------
/apps/basejump-docs/src/mdx/remark.mjs:
--------------------------------------------------------------------------------
1 | import { mdxAnnotations } from 'mdx-annotations'
2 | import remarkGfm from 'remark-gfm'
3 |
4 | export const remarkPlugins = [mdxAnnotations.remark, remarkGfm]
5 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-github-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-github-link.png
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tsconfig",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "publishConfig": {
7 | "access": "public"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-env-variables.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-env-variables.png
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-github-link-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/deploying-vercel/vercel-github-link-2.png
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/react/invite-member-form.mdx:
--------------------------------------------------------------------------------
1 | # InviteMemberForm
2 | A form for creating new invitations for members to a team.
3 |
4 |
5 | This component is not yet implemented.
6 |
--------------------------------------------------------------------------------
/packages/shared/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/library.json",
3 | "compilerOptions": {
4 | "resolveJsonModule": true,
5 | },
6 | "include": ["./src"],
7 | "exclude": ["node_modules"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/basejump-docs/public/images/docs/deploying-vercel/create-vercel-project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/usebasejump/basejump-js/HEAD/apps/basejump-docs/public/images/docs/deploying-vercel/create-vercel-project.png
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/react/HookInstallation.mdx:
--------------------------------------------------------------------------------
1 | This hook is part of the `@usebasejump/react` and `@usebasejump/next` packages. If you've followed the [installation instructions](/docs/react), you should be ready to go!
--------------------------------------------------------------------------------
/apps/basejump-docs/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | 'postcss-focus-visible': {
5 | replaceWith: '[data-focus-visible-added]',
6 | },
7 | autoprefixer: {},
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/react/accept-invitation-form.mdx:
--------------------------------------------------------------------------------
1 | # AcceptInvitationForm
2 | A form for confirming an invitation token is valid and allowing the user to accept it.
3 |
4 |
5 | This component is not yet implemented.
6 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/.env.local.example:
--------------------------------------------------------------------------------
1 | # Update these with your Supabase details from your project settings > API
2 | # https://app.supabase.com/project/_/settings/api
3 | NEXT_PUBLIC_SUPABASE_URL=your-project-url
4 | NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | settings: {
6 | next: {
7 | rootDir: ["apps/*/"],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/utils/supabase/client.ts:
--------------------------------------------------------------------------------
1 | import { createBrowserClient } from '@supabase/ssr'
2 |
3 | export const createClient = () =>
4 | createBrowserClient(
5 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7 | )
8 |
--------------------------------------------------------------------------------
/packages/next/src/utils/create-client.ts:
--------------------------------------------------------------------------------
1 | import {createBrowserClient} from '@supabase/ssr'
2 |
3 | export const createClient = () =>
4 | createBrowserClient(
5 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
6 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7 | )
8 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/lib/remToPx.js:
--------------------------------------------------------------------------------
1 | export function remToPx(remValue) {
2 | let rootFontSize =
3 | typeof window === 'undefined'
4 | ? 16
5 | : parseFloat(window.getComputedStyle(document.documentElement).fontSize)
6 |
7 | return parseFloat(remValue) * rootFontSize
8 | }
9 |
--------------------------------------------------------------------------------
/packages/tsconfig/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Base Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "lib": ["ES2015", "DOM"],
7 | "module": "ESNext",
8 | "target": "es6"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Prose.jsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 |
3 | export function Prose({ as: Component = 'div', className, ...props }) {
4 | return (
5 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/ContentFooter.jsx:
--------------------------------------------------------------------------------
1 | import { SmallPrint } from '@/components/Footer.jsx'
2 |
3 | export function ContentFooter() {
4 | return (
5 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["next", "turbo", "prettier"],
3 | rules: {
4 | "@next/next/no-html-link-for-pages": "off",
5 | },
6 | parserOptions: {
7 | babelOptions: {
8 | presets: [require.resolve("next/babel")],
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/packages/tsconfig/react-library.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "React Library",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "jsx": "react-jsx",
7 | "lib": ["ES2015", "DOM"],
8 | "module": "ESNext",
9 | "target": "es6"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/lib/get-full-url.js:
--------------------------------------------------------------------------------
1 | export default function getFullUrl(path) {
2 | if (path.startsWith("http")) return path;
3 | const baseUrl =
4 | process.env.VERCEL_URL || process.env.NEXT_PUBLIC_VERCEL_URL || window.location.origin;
5 | return [baseUrl, path?.replace(/^\//, "")].filter(Boolean).join("/");
6 | }
--------------------------------------------------------------------------------
/packages/next/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import type {Options} from 'tsup';
2 |
3 | export const tsup: Options = {
4 | dts: true,
5 | entryPoints: ['src/index.tsx'],
6 | external: ['react', /^@usebasejump\//],
7 | format: ['cjs'],
8 | legacyOutput: false,
9 | sourcemap: true,
10 | splitting: false,
11 | bundle: true,
12 | clean: true
13 | };
--------------------------------------------------------------------------------
/packages/react/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import type {Options} from 'tsup';
2 |
3 | export const tsup: Options = {
4 | dts: true,
5 | entryPoints: ['src/index.tsx'],
6 | external: ['react', /^@usebasejump\//],
7 | format: ['cjs'],
8 | legacyOutput: false,
9 | sourcemap: true,
10 | splitting: false,
11 | bundle: true,
12 | clean: true
13 | };
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/BoltIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BoltIcon(props) {
2 | return (
3 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/shared/tsup.config.ts:
--------------------------------------------------------------------------------
1 | import type {Options} from 'tsup';
2 |
3 | export const tsup: Options = {
4 | dts: true,
5 | entryPoints: ['src/index.ts'],
6 | external: ['react', 'next', /^@usebasejump\//],
7 | format: ['cjs', 'esm'],
8 | legacyOutput: false,
9 | sourcemap: true,
10 | splitting: false,
11 | bundle: true,
12 | clean: true
13 | };
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 | # @usebasejump/react
2 |
3 | Convenience functions, hooks and types for working with [Basejump](https://usebasejump.com) in React.
4 |
5 | Learn more on the [Basejump React Docs](https://usebasejump.com/docs/react).
6 |
7 | ## Installation
8 |
9 | ```bash
10 | yarn add @usebasejump/react
11 | ```
12 |
13 | or
14 |
15 | ```bash
16 | npm install @usebasejump/react
17 | ```
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-accounts.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccounts as useAccountsReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccounts = (options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountsReact(supabaseClient, options);
10 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/SocialLink.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 |
3 | export function SocialLink({ href, icon: Icon, children }) {
4 | return (
5 |
6 | {children}
7 |
8 |
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/packages/eslint-config-custom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-config-custom",
3 | "version": "0.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "dependencies": {
7 | "eslint-config-next": "^13.4.1",
8 | "eslint-config-prettier": "^8.3.0",
9 | "eslint-plugin-react": "7.28.0",
10 | "eslint-config-turbo": "^1.9.3"
11 | },
12 | "publishConfig": {
13 | "access": "public"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccount as useAccountReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccount = (accountId: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountReact(supabaseClient, accountId, options);
10 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ShirtIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ShirtIcon(props) {
2 | return (
3 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-personal-account.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {usePersonalAccount as usePersonalAccountReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const usePersonalAccount = (options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return usePersonalAccountReact(supabaseClient, options);
10 | }
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account-by-slug.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccountBySlug as useAccountBySlugReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccountBySlug = (slug: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountBySlugReact(supabaseClient, slug, options);
10 | }
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-billing-plans.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useBillingPlans as useBillingPlansReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useBillingPlans = (accountId: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useBillingPlansReact(supabaseClient, accountId, options);
10 | }
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account-members.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccountMembers as useAccountMembersReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccountMembers = (accountId: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountMembersReact(supabaseClient, accountId, options);
10 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/LinkIcon.jsx:
--------------------------------------------------------------------------------
1 | export function LinkIcon(props) {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/MagnifyingGlassIcon.jsx:
--------------------------------------------------------------------------------
1 | export function MagnifyingGlassIcon(props) {
2 | return (
3 |
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account-invitations.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccountInvitations as useAccountInvitationsReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccountInvitations = (accountId: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountInvitationsReact(supabaseClient, accountId, options);
10 | }
--------------------------------------------------------------------------------
/packages/react/src/components/signed-out.tsx:
--------------------------------------------------------------------------------
1 | import {useBasejumpSession} from "./basejump-user-session";
2 | import {ReactNode} from "react";
3 |
4 | type Props = {
5 | children: ReactNode;
6 | }
7 | /**
8 | * This component will only render its children if the user is logged out.
9 | * @param children
10 | */
11 | export const SignedOut = ({children}: Props) => {
12 | const session = useBasejumpSession();
13 |
14 | return !!session ? null : <>{children}>;
15 | }
16 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/dashboard/DashboardTitle.tsx:
--------------------------------------------------------------------------------
1 | interface Props {
2 | title: string;
3 | description: string;
4 | }
5 | export default function DashboardTitle({title, description}: Props) {
6 | return (
7 |
8 |
{title}
9 |
10 | {description}
11 |
12 |
13 | )
14 | }
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account-billing-status.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccountBillingStatus as useAccountBillingStatusReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccountBillingStatus = (accountId: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountBillingStatusReact(supabaseClient, accountId, options);
10 | }
--------------------------------------------------------------------------------
/packages/next/README.md:
--------------------------------------------------------------------------------
1 | # @usebasejump/next
2 |
3 | Convenience functions, hooks and types for working with [Basejump](https://usebasejump.com) in Next.js.
4 |
5 | Basically a shim over `@usebasejump/react` that handles initializing the supabase cient for you.
6 |
7 | Learn more on the [Basejump React Docs](https://usebasejump.com/docs/react).
8 |
9 | ## Installation
10 |
11 | ```bash
12 | yarn add @usebasejump/next
13 | ```
14 |
15 | or
16 |
17 | ```bash
18 | npm install @usebasejump/next
19 | ```
--------------------------------------------------------------------------------
/packages/next/src/hooks/use-account-invitation-lookup.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {createClient} from "../utils/create-client";
4 | import {useAccountInvitationLookup as useAccountInvitationLookupReact} from "@usebasejump/react";
5 | import {SWRConfiguration} from "swr";
6 |
7 | export const useAccountInvitationLookup = (token: string, options?: SWRConfiguration) => {
8 | const supabaseClient = createClient();
9 | return useAccountInvitationLookupReact(supabaseClient, token, options);
10 | }
--------------------------------------------------------------------------------
/packages/react/src/components/signed-in.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {useBasejumpSession} from "./basejump-user-session";
4 | import {ReactNode} from "react";
5 |
6 | type Props = {
7 | children: ReactNode;
8 | }
9 |
10 | /**
11 | * This component will only render its children if the user is signed in.
12 | * @param children
13 | */
14 | export const SignedIn = ({children}: Props) => {
15 | const session = useBasejumpSession();
16 |
17 | return !!session ? <>{children}> : null;
18 | }
19 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/Logo.tsx:
--------------------------------------------------------------------------------
1 | export default function Logo() {
2 | return (
3 |
10 | )
11 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/CheckIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CheckIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/BookIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BookIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/PaperAirplaneIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PaperAirplaneIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 |
4 | export default async function PersonalAccountSettingsPage() {
5 | const cookieStore = cookies();
6 | const supabaseClient = createClient(cookieStore);
7 | const {data: personalAccount} = await supabaseClient.rpc('get_personal_account');
8 |
9 | return (
10 |
11 | Personal Account
12 |
13 | )
14 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/DocumentIcon.jsx:
--------------------------------------------------------------------------------
1 | export function DocumentIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Logo.jsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 |
3 | export function Logo(props) {
4 | return (
5 |
6 |
12 |
13 | Basejump
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/FaceSmileIcon.jsx:
--------------------------------------------------------------------------------
1 | export function FaceSmileIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/mdx/recma.mjs:
--------------------------------------------------------------------------------
1 | import { mdxAnnotations } from 'mdx-annotations'
2 | import recmaNextjsStaticProps from 'recma-nextjs-static-props'
3 |
4 | function recmaRemoveNamedExports() {
5 | return (tree) => {
6 | tree.body = tree.body.map((node) => {
7 | if (node.type === 'ExportNamedDeclaration') {
8 | return node.declaration
9 | }
10 | return node
11 | })
12 | }
13 | }
14 |
15 | export const recmaPlugins = [
16 | mdxAnnotations.recma,
17 | recmaRemoveNamedExports,
18 | recmaNextjsStaticProps,
19 | ]
20 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ListIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ListIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/PaperClipIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PaperClipIcon(props) {
2 | return (
3 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "turbo run build",
5 | "dev": "turbo run dev",
6 | "lint": "turbo run lint",
7 | "format": "prettier --write \"**/*.{ts,tsx,md}\""
8 | },
9 | "devDependencies": {
10 | "@turbo/gen": "^1.9.7",
11 | "eslint": "^7.32.0",
12 | "eslint-config-custom": "*",
13 | "prettier": "^2.5.1",
14 | "turbo": "^1.10.13"
15 | },
16 | "name": "basejump-js",
17 | "packageManager": "yarn@1.22.19",
18 | "workspaces": [
19 | "apps/*",
20 | "packages/*"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/PackageIcon.jsx:
--------------------------------------------------------------------------------
1 | export function PackageIcon(props) {
2 | return (
3 |
15 | )
16 | }
17 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 |
4 | export default async function TeamSettingsPage({params: {accountSlug}}) {
5 | const cookieStore = cookies();
6 | const supabaseClient = createClient(cookieStore);
7 | const {data: teamAccount} = await supabaseClient.rpc('get_account_by_slug', {
8 | slug: accountSlug
9 | });
10 |
11 | return (
12 |
13 | Team Settings
14 |
15 | )
16 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/AuthenticatedNavigation.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {BasejumpUserSession, SignedIn, SignedOut} from "@usebasejump/next";
4 | import Link from "next/link";
5 |
6 | export default function AuthenticatedNavigation() {
7 | return (
8 |
9 |
10 | Dashboard
11 |
12 |
13 | Login
14 |
15 |
16 | )
17 | }
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@usebasejump/shared",
3 | "version": "0.0.3",
4 | "main": "dist/index.js",
5 | "module": "dist/index.mjs",
6 | "types": "dist/index.d.ts",
7 | "publishConfig": {
8 | "access": "public"
9 | },
10 | "files": [
11 | "dist"
12 | ],
13 | "scripts": {
14 | "lint": "tsc --noEmit",
15 | "build": "tsup",
16 | "dev": "tsup --watch --clean"
17 | },
18 | "author": "Tiniscule",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "tsconfig": "workspace:*",
22 | "tsup": "^8.0.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ChevronRightLeftIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ChevronRightLeftIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/CopyIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CopyIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/packages/next/src/components/basejump-user-session.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {ReactNode} from "react";
4 | import {BasejumpUserSession as BasejumpUserSessionReact} from "@usebasejump/react";
5 | import {createClient} from "../utils/create-client";
6 |
7 | type Props = {
8 | children: ReactNode;
9 | };
10 |
11 | export const BasejumpUserSession = ({
12 | children
13 | }: Props) => {
14 | const supabaseClient = createClient();
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | };
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/ruby.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/EnvelopeIcon.jsx:
--------------------------------------------------------------------------------
1 | export function EnvelopeIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/BellIcon.jsx:
--------------------------------------------------------------------------------
1 | export function BellIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/CartIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CartIcon(props) {
2 | return (
3 |
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/packages/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "composite": false,
6 | "declaration": true,
7 | "declarationMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "inlineSources": false,
11 | "isolatedModules": true,
12 | "moduleResolution": "node",
13 | "noUnusedLocals": false,
14 | "noUnusedParameters": false,
15 | "preserveWatchOutput": true,
16 | "skipLibCheck": true,
17 | "strict": true
18 | },
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/billing-new.mdx:
--------------------------------------------------------------------------------
1 | import {Note} from "@/components/mdx";
2 |
3 | export const description = 'Learn how to create new billing providers for Basejump.'
4 |
5 | # Adding your own billing providers
6 |
7 | Basejump provides support for Stripe out of the box, but you can add your own billing providers if you need to. Billing providers must support:
8 |
9 | - Portal interfaces for customers to manage their subscription or update payment settings
10 | - Subscriptions with support for multiple product plans
11 | - Webhooks for updating subscription status
12 |
13 |
14 | Documentation coming soon(ish)
15 |
--------------------------------------------------------------------------------
/packages/tsconfig/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "plugins": [{ "name": "next" }],
7 | "allowJs": true,
8 | "declaration": false,
9 | "declarationMap": false,
10 | "incremental": true,
11 | "jsx": "preserve",
12 | "lib": ["dom", "dom.iterable", "esnext"],
13 | "module": "esnext",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "strict": false,
17 | "target": "es5"
18 | },
19 | "include": ["src", "next-env.d.ts"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ChatBubbleIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ChatBubbleIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ShapesIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ShapesIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/Step.tsx:
--------------------------------------------------------------------------------
1 | export default function Step({
2 | title,
3 | children,
4 | }: {
5 | title: string
6 | children: React.ReactNode
7 | }) {
8 | return (
9 |
10 |
11 |
17 |
20 | {children}
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-accounts.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountsResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccounts = (supabaseClient: SupabaseClient, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && ["accounts"],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("get_accounts");
10 |
11 | if (error) {
12 | throw new Error(error.message);
13 | }
14 |
15 | return data;
16 | },
17 | options
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": [
4 | "**/.env.*local"
5 | ],
6 | "pipeline": {
7 | "build": {
8 | "dependsOn": [
9 | "^build"
10 | ],
11 | "outputs": [
12 | ".next/**",
13 | "!.next/cache/**"
14 | ],
15 | "env": [
16 | "NEXT_PUBLIC_BASEJUMP_INVITATION_URL_TEMPLATE",
17 | "NEXT_PUBLIC_SUPABASE_ANON_KEY",
18 | "NEXT_PUBLIC_SUPABASE_URL",
19 | "VERCEL_URL",
20 | "NEXT_PUBLIC_VERCEL_URL"
21 | ]
22 | },
23 | "lint": {},
24 | "dev": {
25 | "cache": false,
26 | "persistent": true
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-personal-account.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const usePersonalAccount = (supabaseClient: SupabaseClient, options?: SWRConfiguration) => {
6 | return useSWR(
7 | ["personal-account"],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("get_personal_account");
10 |
11 | if (error) {
12 | throw new Error(error.message);
13 | }
14 | return data;
15 | },
16 | options,
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/packages/react/src/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Convenience functions
3 | */
4 | export * from "./hooks/use-accounts";
5 | export * from "./hooks/use-account";
6 | export * from "./hooks/use-account-by-slug";
7 | export * from "./hooks/use-personal-account";
8 | export * from "./hooks/use-account-members";
9 | export * from "./hooks/use-account-invitations";
10 | export * from "./hooks/use-account-invitation-lookup";
11 | export * from "./hooks/use-account-billing-status";
12 | export * from "./hooks/use-billing-plans";
13 | export * from "./components/basejump-user-session";
14 | export * from "./components/signed-out";
15 | export * from "./components/signed-in";
16 |
17 | export * from "@usebasejump/shared";
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/SquaresPlusIcon.jsx:
--------------------------------------------------------------------------------
1 | export function SquaresPlusIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/NavigatingAccountSelector.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import {useRouter} from "next/navigation";
4 | import AccountSelector from "@/components/basejump/AccountSelector.tsx";
5 |
6 | interface Props {
7 | accountId: string;
8 | }
9 | export default function NavigatingAccountSelector({accountId}: Props) {
10 | const router = useRouter();
11 |
12 | return (
13 | router.push(`/dashboard/${account.slug}`)}
16 | onAccountSelected={(account) => router.push(account.personal_account ? `/dashboard` : `/dashboard/${account.slug}`)}
17 | />
18 | )
19 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/ClipboardIcon.jsx:
--------------------------------------------------------------------------------
1 | export function ClipboardIcon(props) {
2 | return (
3 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/MapPinIcon.jsx:
--------------------------------------------------------------------------------
1 | export function MapPinIcon(props) {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/CalendarIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CalendarIcon(props) {
2 | return (
3 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/apps/basejump-docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": [
4 | "dom",
5 | "dom.iterable",
6 | "esnext"
7 | ],
8 | "allowJs": true,
9 | "skipLibCheck": true,
10 | "strict": false,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "incremental": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "preserve",
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": [
25 | "next-env.d.ts",
26 | "**/*.ts",
27 | "**/*.tsx"
28 | ],
29 | "exclude": [
30 | "node_modules"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/apps/basejump-docs/next.config.mjs:
--------------------------------------------------------------------------------
1 | import nextMDX from '@next/mdx'
2 |
3 | import { recmaPlugins } from './src/mdx/recma.mjs'
4 | import { rehypePlugins } from './src/mdx/rehype.mjs'
5 | import { remarkPlugins } from './src/mdx/remark.mjs'
6 | import withSearch from './src/mdx/search.mjs'
7 |
8 | const withMDX = nextMDX({
9 | options: {
10 | remarkPlugins,
11 | rehypePlugins,
12 | recmaPlugins,
13 | providerImportSource: '@mdx-js/react',
14 | },
15 | })
16 |
17 | /** @type {import('next').NextConfig} */
18 | const nextConfig = {
19 | reactStrictMode: true,
20 | pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'],
21 | experimental: {
22 | scrollRestoration: true,
23 | },
24 | }
25 |
26 | export default withSearch(withMDX(nextConfig))
27 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/styles/tailwind.css:
--------------------------------------------------------------------------------
1 | @layer base {
2 | :root {
3 | --shiki-color-text: theme('colors.white');
4 | --shiki-token-constant: theme('colors.emerald.300');
5 | --shiki-token-string: theme('colors.emerald.300');
6 | --shiki-token-comment: theme('colors.zinc.500');
7 | --shiki-token-keyword: theme('colors.sky.300');
8 | --shiki-token-parameter: theme('colors.pink.300');
9 | --shiki-token-function: theme('colors.violet.300');
10 | --shiki-token-string-expression: theme('colors.emerald.300');
11 | --shiki-token-punctuation: theme('colors.zinc.200');
12 | }
13 |
14 | [inert] ::-webkit-scrollbar {
15 | display: none;
16 | }
17 | }
18 |
19 | @tailwind base;
20 | @tailwind components;
21 | @tailwind utilities;
22 |
--------------------------------------------------------------------------------
/packages/next/src/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Convenience functions
3 | */
4 | export * from "./hooks/use-accounts";
5 | export * from "./hooks/use-account";
6 | export * from "./hooks/use-account-by-slug";
7 | export * from "./hooks/use-personal-account";
8 | export * from "./hooks/use-account-members";
9 | export * from "./hooks/use-account-invitations";
10 | export * from "./hooks/use-account-invitation-lookup";
11 | export * from "./hooks/use-account-billing-status";
12 | export * from "./hooks/use-billing-plans";
13 | export * from "./components/basejump-user-session";
14 |
15 | /**
16 | * No changes to these
17 | */
18 | export { SignedOut, SignedIn, useBasejumpSession, useBasejumpClient, useBasejumpProvider } from '@usebasejump/react';
19 | export * from '@usebasejump/shared';
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccount = (supabaseClient: SupabaseClient, accountId: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!accountId && ["account", accountId],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("get_account", {
10 | account_id: accountId,
11 | });
12 |
13 | if (error) {
14 | throw new Error(error.message);
15 | }
16 |
17 | return data;
18 | }, options);
19 | };
20 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./src/*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account-by-slug.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccountBySlug = (supabaseClient: SupabaseClient, accountSlug: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!accountSlug && ["account", accountSlug],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("get_account_by_slug", {
10 | slug: accountSlug,
11 | });
12 |
13 | if (error) {
14 | throw new Error(error.message);
15 | }
16 |
17 | return data;
18 | }, options);
19 | };
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # project dependencies
4 | .idea
5 |
6 | # dependencies
7 | /node_modules
8 | node_modules
9 | /.pnp
10 | .pnp.js
11 |
12 | # testing
13 | /coverage
14 |
15 | # next.js
16 | /.next/
17 | /out/
18 |
19 | # production
20 | /build
21 | dist/
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # local env files
34 | .envrc
35 | .env
36 |
37 | # vercel
38 | .vercel
39 |
40 | # typescript
41 | *.tsbuildinfo
42 | next-env.d.ts
43 |
44 | # Supabase
45 | **/supabase/.branches
46 | **/supabase/.temp
47 |
48 | # turbo
49 | .turbo
50 |
51 | apps/nextjs-starter/supabase/config.toml
52 | apps/nextjs-starter/supabase/seed.sql
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/FolderIcon.jsx:
--------------------------------------------------------------------------------
1 | export function FolderIcon(props) {
2 | return (
3 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Basejump React / Next.js Helpers
2 |
3 | Basejump is a set of Postgres migrations and edge functions that give you personal account, teams, permissions and billing for Supabase - you don't need anything but the basic installation listed at [usebasejump.com](https://usebasejump.com). However, there are also some convenience methods including data fetching hooks leveraging [SWR](https://swr.vercel.app), Typescript Types and other convenience components.
4 |
5 | These helpers are also used inside the copy/paste UI components provided at [usebasejump.com/docs/react](https://usebasejump.com/docs/react) - however you're welcome to refactor them a bit if you want to skip the external dependency. It wouldn't be too hard.
6 |
7 | [Learn more, including installation info at usebasejump.com](https://usebasejump.com/docs/react).
8 |
9 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account-members.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountMembersResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccountMembers = (supabaseClient: SupabaseClient, accountId: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!accountId && ["account-members", accountId],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("get_account_members", {
10 | account_id: accountId,
11 | });
12 |
13 | if (error) {
14 | throw new Error(error.message);
15 | }
16 |
17 | return data;
18 | },
19 | options
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account-invitation-lookup.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {LookupInvitationResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccountInvitationLookup = (supabaseClient: SupabaseClient, token: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!token && ["account-invitation-lookup", token],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc("lookup_invitation", {
10 | lookup_invitation_token: token,
11 | });
12 |
13 | if (error) {
14 | throw new Error(error.message);
15 | }
16 |
17 | return data;
18 | },
19 | options);
20 | };
21 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import {GeistSans} from 'geist/font/sans'
2 | import './globals.css'
3 |
4 | const defaultUrl = process.env.VERCEL_URL
5 | ? `https://${process.env.VERCEL_URL}`
6 | : 'http://localhost:3000'
7 |
8 | export const metadata = {
9 | metadataBase: new URL(defaultUrl),
10 | title: 'Next.js, Supabase and Basejump Starter Kit',
11 | description: 'The fastest way to build apps with Next.js, Supabase and Basejump',
12 | }
13 |
14 | export default function RootLayout({
15 | children,
16 | }: {
17 | children: React.ReactNode
18 | }) {
19 | return (
20 |
21 |
22 |
23 | {children}
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/SupabaseExamples.mdx:
--------------------------------------------------------------------------------
1 | import {CodeGroup} from "@/components/mdx.jsx";
2 |
3 |
4 |
5 |
6 | ```plsql
7 | -- Create the tasks table
8 | CREATE TABLE IF NOT EXISTS tasks (
9 | id INTEGER PRIMARY KEY AUTOINCREMENT,
10 | account_id uuid NOT NULL REFERENCES basejump.accounts(id),
11 | title TEXT NOT NULL,
12 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP
13 | );
14 |
15 | -- enable RLS on tasks table
16 | ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
17 |
18 | -- allow team members to view tasks
19 | CREATE POLICY "Only members can view tasks" on tasks
20 | FOR SELECT
21 | TO authenticated
22 | USING (
23 | account_id IN (SELECT basejump.get_accounts_for_current_user())
24 | );
25 |
26 | ```
27 |
28 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account-invitations.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountInvitesResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccountInvitations = (supabaseClient: SupabaseClient, accountId: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!accountId && ["account-invitations", accountId],
8 | async () => {
9 | const {data, error} = await supabaseClient.rpc(
10 | "get_account_invitations",
11 | {
12 | account_id: accountId,
13 | }
14 | );
15 |
16 | if (error) {
17 | throw new Error(error.message);
18 | }
19 |
20 | return data;
21 | },
22 | options
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/python.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/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 "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@usebasejump/react",
3 | "version": "0.0.5",
4 | "main": "dist/index.js",
5 | "module": "dist/index.mjs",
6 | "types": "dist/index.d.ts",
7 | "files": [
8 | "dist"
9 | ],
10 | "publishConfig": {
11 | "access": "public"
12 | },
13 | "license": "MIT",
14 | "scripts": {
15 | "lint": "tsc --noEmit",
16 | "build": "tsup",
17 | "dev": "tsup --watch --clean"
18 | },
19 | "peerDependencies": {
20 | "@supabase/supabase-js": "^2.21.0"
21 | },
22 | "dependencies": {
23 | "@usebasejump/shared": "*",
24 | "swr": "^2.2.4"
25 | },
26 | "devDependencies": {
27 | "@types/react": "^18.2.0",
28 | "@types/react-dom": "^18.2.0",
29 | "eslint": "^7.32.0",
30 | "eslint-config-custom": "*",
31 | "react": "^18.2.0",
32 | "react-dom": "^18.2.0",
33 | "tsconfig": "*",
34 | "tsup": "^8.0.1",
35 | "typescript": "^4.5.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/auth/callback/route.ts:
--------------------------------------------------------------------------------
1 | import {createClient} from '@/utils/supabase/server.ts'
2 | import {NextResponse} from 'next/server'
3 | import {cookies} from 'next/headers'
4 |
5 | export async function GET(request: Request) {
6 | // The `/auth/callback` route is required for the server-side auth flow implemented
7 | // by the Auth Helpers package. It exchanges an auth code for the user's session.
8 | // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange
9 | const requestUrl = new URL(request.url)
10 | const code = requestUrl.searchParams.get('code')
11 |
12 | if (code) {
13 | const cookieStore = cookies()
14 | const supabase = createClient(cookieStore)
15 | await supabase.auth.exchangeCodeForSession(code)
16 | }
17 |
18 | // URL to redirect to after sign in process completes
19 | return NextResponse.redirect(requestUrl.origin)
20 | }
21 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/UsersIcon.jsx:
--------------------------------------------------------------------------------
1 | export function UsersIcon(props) {
2 | return (
3 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/UserIcon.jsx:
--------------------------------------------------------------------------------
1 | export function UserIcon(props) {
2 | return (
3 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/layout.tsx:
--------------------------------------------------------------------------------
1 | import {createClient} from "@/utils/supabase/server.ts";
2 | import DashboardHeader from "@/app/dashboard/DashboardHeader.tsx";
3 | import {cookies} from "next/headers";
4 |
5 | export default async function PersonalAccountDashboard({children}) {
6 |
7 | const cookieStore = cookies()
8 | const supabaseClient = createClient(cookieStore);
9 |
10 | const {data: personalAccount, error} = await supabaseClient.rpc('get_personal_account');
11 |
12 | const navigation = [
13 | {
14 | name: 'Overview',
15 | href: '/dashboard',
16 | },
17 | {
18 | name: 'Settings',
19 | href: '/dashboard/settings'
20 | }
21 | ]
22 |
23 | return (
24 | <>
25 |
26 | {children}
27 | >
28 | )
29 |
30 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/javascript.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@usebasejump/next",
3 | "version": "0.0.4",
4 | "main": "dist/index.js",
5 | "module": "dist/index.mjs",
6 | "types": "dist/index.d.ts",
7 | "files": [
8 | "dist"
9 | ],
10 | "publishConfig": {
11 | "access": "public"
12 | },
13 | "license": "MIT",
14 | "scripts": {
15 | "lint": "tsc --noEmit",
16 | "build": "tsup",
17 | "dev": "tsup --watch --clean"
18 | },
19 | "peerDependencies": {
20 | "@supabase/ssr": "^0.0.10",
21 | "@supabase/supabase-js": "^2.21.0"
22 | },
23 | "dependencies": {
24 | "@usebasejump/react": "*",
25 | "swr": "^2.2.4"
26 | },
27 | "devDependencies": {
28 | "@supabase/ssr": "^0.0.10",
29 | "@supabase/supabase-js": "^2.21.0",
30 | "@types/react": "^18.2.0",
31 | "@types/react-dom": "^18.2.0",
32 | "eslint": "^7.32.0",
33 | "eslint-config-custom": "*",
34 | "react": "^18.2.0",
35 | "react-dom": "^18.2.0",
36 | "tsconfig": "*",
37 | "tsup": "^8.0.1",
38 | "typescript": "^4.5.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/TagIcon.jsx:
--------------------------------------------------------------------------------
1 | export function TagIcon(props) {
2 | return (
3 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/layout.tsx:
--------------------------------------------------------------------------------
1 | import {createClient} from "@/utils/supabase/server.ts";
2 | import DashboardHeader from "@/app/dashboard/DashboardHeader.tsx";
3 | import {cookies} from "next/headers";
4 |
5 | export default async function PersonalAccountDashboard({children, params: {accountSlug}}) {
6 | const cookieStore = cookies()
7 | const supabaseClient = createClient(cookieStore);
8 |
9 | const {data: teamAccount, error} = await supabaseClient.rpc('get_account_by_slug', {
10 | slug: accountSlug
11 | });
12 |
13 | const navigation = [
14 | {
15 | name: 'Overview',
16 | href: `/dashboard/${accountSlug}`,
17 | },
18 | {
19 | name: 'Settings',
20 | href: `/dashboard/${accountSlug}/settings`
21 | }
22 | ]
23 |
24 | return (
25 | <>
26 |
27 | {children}
28 | >
29 | )
30 |
31 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/404.jsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 |
3 | import { Button } from '@/components/Button'
4 | import { HeroPattern } from '@/components/HeroPattern'
5 |
6 | export default function NotFound() {
7 | return (
8 | <>
9 |
10 | Page not found - Basejump
11 |
12 |
13 |
14 |
15 | 404
16 |
17 |
18 | Page not found
19 |
20 |
21 | Sorry, we couldn’t find the page you’re looking for.
22 |
23 |
26 |
27 | >
28 | )
29 | }
30 |
31 | NotFound.layoutProps = { showFeedbackForm: false }
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2022 usebasejump.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/apps/nextjs-starter/supabase/functions/billing-webhooks/index.ts:
--------------------------------------------------------------------------------
1 | /***
2 | * THESE FUNCTIONS ARE FOR TESTING PURPOSES ONLY.
3 | * TO SETUP ON YOUR OWN, HEAD TO https://usebasejump.com
4 | */
5 |
6 | import {serve} from "https://deno.land/std@0.168.0/http/server.ts";
7 | import {billingWebhooksWrapper, stripeWebhookHandler} from "https://deno.land/x/basejump@v2.0.3/billing-functions/mod.ts";
8 |
9 |
10 | import Stripe from "https://esm.sh/stripe@11.1.0?target=deno";
11 |
12 | const stripeClient = new Stripe(Deno.env.get("STRIPE_API_KEY") as string, {
13 | // This is needed to use the Fetch API rather than relying on the Node http
14 | // package.
15 | apiVersion: "2022-11-15",
16 | httpClient: Stripe.createFetchHttpClient(),
17 | });
18 |
19 | const stripeResponse = stripeWebhookHandler({
20 | stripeClient,
21 | stripeWebhookSigningSecret: Deno.env.get("STRIPE_WEBHOOK_SIGNING_SECRET") as string,
22 | });
23 |
24 | const webhookEndpoint = billingWebhooksWrapper(stripeResponse);
25 |
26 | serve(async (req) => {
27 | const response = await webhookEndpoint(req);
28 | return response;
29 | });
30 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-account-billing-status.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetAccountBillingStatusResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useAccountBillingStatus = (supabaseClient: SupabaseClient, accountId: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && !!accountId && ['account-billing-status', accountId],
8 | async () => {
9 | const {data, error} = await supabaseClient.functions.invoke(
10 | "billing-functions",
11 | {
12 | body: {
13 | action: "get_billing_status",
14 | args: {
15 | account_id: accountId,
16 | },
17 | },
18 | }
19 | );
20 |
21 | if (error) {
22 | throw new Error(error.message);
23 | }
24 |
25 | return data;
26 | },
27 | options);
28 | };
29 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/index.mdx:
--------------------------------------------------------------------------------
1 | import {Guides} from '@/components/Guides.jsx'
2 | import {Resources} from '@/components/Resources.jsx'
3 | import {HeroPattern} from '@/components/HeroPattern.jsx'
4 |
5 | export const description =
6 | 'Learn everything there is to know about using Basejump to build your next great product.'
7 |
8 | export const sections = [
9 | {title: 'Guides', id: 'guides'},
10 | {title: 'Resources', id: 'resources'},
11 | ]
12 |
13 |
14 |
15 | # Get started with Basejump
16 |
17 | Basejump adds personal accounts, team accounts, invitations and subscription billing to your Supabase project. Use it as a launching point for your Supabase app, or as a standalone auth API similar to Auth0 or Clerk.
18 |
19 | We offer a React UI Kit to help you get started, but you can use Basejump with any frontend framework or platform.
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/settings/layout.tsx:
--------------------------------------------------------------------------------
1 | import SettingsNavigation from "@/components/dashboard/SettingsNavigation";
2 | import DashboardTitle from "@/components/dashboard/DashboardTitle";
3 | import {Separator} from "@/components/ui/separator.tsx";
4 |
5 | export default function PersonalAccountSettingsPage({children}) {
6 | const items = [
7 | { name: "Profile", href: "/dashboard/settings" },
8 | { name: "Teams", href: "/dashboard/settings/teams" },
9 | { name: "Billing", href: "/dashboard/settings/billing" },
10 | ]
11 | return (
12 |
13 |
14 |
15 |
16 |
19 |
{children}
20 |
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/GridPattern.jsx:
--------------------------------------------------------------------------------
1 | import { useId } from 'react'
2 |
3 | export function GridPattern({ width, height, x, y, squares, ...props }) {
4 | let patternId = useId()
5 |
6 | return (
7 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/packages/react/src/hooks/use-billing-plans.ts:
--------------------------------------------------------------------------------
1 | import {SupabaseClient} from "@supabase/supabase-js";
2 | import {GetBillingPlansResponse} from "@usebasejump/shared";
3 | import useSWR, {SWRConfiguration} from "swr";
4 |
5 | export const useBillingPlans = (supabaseClient: SupabaseClient, accountId?: string, options?: SWRConfiguration) => {
6 | return useSWR(
7 | !!supabaseClient && ["billing-plans", accountId],
8 | async () => {
9 | if (!supabaseClient) {
10 | throw new Error("Client not yet loaded");
11 | }
12 |
13 | const {data, error} = await supabaseClient.functions.invoke(
14 | "billing-functions",
15 | {
16 | body: {
17 | action: "get_plans",
18 | args: {
19 | account_id: accountId,
20 | },
21 | },
22 | }
23 | );
24 |
25 | if (error) {
26 | throw new Error(error.message);
27 | }
28 |
29 | return data;
30 | },
31 | options
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/AuthButton.tsx:
--------------------------------------------------------------------------------
1 | import {createClient} from '@/utils/supabase/server.ts'
2 | import Link from 'next/link'
3 | import {cookies} from 'next/headers'
4 | import {redirect} from 'next/navigation'
5 |
6 | export default async function AuthButton() {
7 | const cookieStore = cookies()
8 | const supabase = createClient(cookieStore)
9 |
10 | const {
11 | data: { user },
12 | } = await supabase.auth.getUser()
13 |
14 | const signOut = async () => {
15 | 'use server'
16 |
17 | const cookieStore = cookies()
18 | const supabase = createClient(cookieStore)
19 | await supabase.auth.signOut()
20 | return redirect('/login')
21 | }
22 |
23 | return user ? (
24 |
25 | Hey, {user.email}!
26 |
31 |
32 | ) : (
33 |
37 | Login
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/settings/layout.tsx:
--------------------------------------------------------------------------------
1 | import SettingsNavigation from "@/components/dashboard/SettingsNavigation";
2 | import DashboardTitle from "@/components/dashboard/DashboardTitle";
3 | import {Separator} from "@/components/ui/separator.tsx";
4 |
5 | export default function TeamSettingsPage({children, params: {accountSlug}}) {
6 | const items = [
7 | { name: "Account", href: `/dashboard/${accountSlug}/settings` },
8 | { name: "Members", href: `/dashboard/${accountSlug}/settings/members` },
9 | { name: "Billing", href: `/dashboard/${accountSlug}/settings/billing` },
10 | ]
11 | return (
12 |
13 |
14 |
15 |
16 |
19 |
{children}
20 |
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/supabase/functions/billing-functions/index.ts:
--------------------------------------------------------------------------------
1 | /***
2 | * THESE FUNCTIONS ARE FOR TESTING PURPOSES ONLY.
3 | * TO SETUP ON YOUR OWN, HEAD TO https://usebasejump.com
4 | */
5 | import {serve} from "https://deno.land/std@0.168.0/http/server.ts";
6 | import {billingFunctionsWrapper, stripeFunctionHandler} from "https://deno.land/x/basejump@v2.0.3/billing-functions/mod.ts";
7 |
8 | import Stripe from "https://esm.sh/stripe@11.1.0?target=deno";
9 |
10 | const stripeClient = new Stripe(Deno.env.get("STRIPE_API_KEY") as string, {
11 | // This is needed to use the Fetch API rather than relying on the Node http
12 | // package.
13 | apiVersion: "2022-11-15",
14 | httpClient: Stripe.createFetchHttpClient(),
15 | });
16 |
17 | const stripeHandler = stripeFunctionHandler({
18 | stripeClient,
19 | defaultPlanId: Deno.env.get("STRIPE_DEFAULT_PLAN_ID") as string,
20 | defaultTrialDays: Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS") ? Number(Deno.env.get("STRIPE_DEFAULT_TRIAL_DAYS")) : undefined
21 | });
22 |
23 | const billingEndpoint = billingFunctionsWrapper(stripeHandler, {
24 | allowedURLs: ['http://localhost:3000']
25 | });
26 |
27 | serve(async (req) => {
28 | const response = await billingEndpoint(req);
29 |
30 | return response;
31 | });
32 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/settings/billing/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 | import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx";
4 | import {Card} from "@/components/ui/card.tsx";
5 |
6 | export default async function TeamBillingPage({params: {accountSlug}}) {
7 | const cookieStore = cookies();
8 | const supabaseClient = createClient(cookieStore);
9 | const {data: teams} = await supabaseClient.rpc('get_accounts');
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | Team
18 |
19 |
20 |
21 | {teams.map((team) => (
22 |
23 | {team.name}
24 |
25 | ))}
26 |
27 |
28 |
29 |
30 | )
31 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/[accountSlug]/settings/members/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 | import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx";
4 | import {Card} from "@/components/ui/card.tsx";
5 |
6 | export default async function TeamMembersPage({params: {accountSlug}}) {
7 | const cookieStore = cookies();
8 | const supabaseClient = createClient(cookieStore);
9 | const {data: teams} = await supabaseClient.rpc('get_accounts');
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | Team
18 |
19 |
20 |
21 | {teams.map((team) => (
22 |
23 | {team.name}
24 |
25 | ))}
26 |
27 |
28 |
29 |
30 | )
31 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/settings/teams/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 | import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx";
4 | import {Card} from "@/components/ui/card.tsx";
5 |
6 | export default async function PersonalAccountTeamsPage({params: {accountSlug}}) {
7 | const cookieStore = cookies();
8 | const supabaseClient = createClient(cookieStore);
9 | const {data: teams} = await supabaseClient.rpc('get_accounts');
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | Team
18 |
19 |
20 |
21 | {teams.map((team) => (
22 |
23 | {team.name}
24 |
25 | ))}
26 |
27 |
28 |
29 |
30 | )
31 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/(personalAccount)/settings/billing/page.tsx:
--------------------------------------------------------------------------------
1 | import {cookies} from "next/headers";
2 | import {createClient} from "@/utils/supabase/server.ts";
3 | import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx";
4 | import {Card} from "@/components/ui/card.tsx";
5 |
6 | export default async function PersonalAccountBillingPage({params: {accountSlug}}) {
7 | const cookieStore = cookies();
8 | const supabaseClient = createClient(cookieStore);
9 | const {data: teams} = await supabaseClient.rpc('get_accounts');
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | Team
18 |
19 |
20 |
21 | {teams.map((team) => (
22 |
23 | {team.name}
24 |
25 | ))}
26 |
27 |
28 |
29 |
30 | )
31 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import GettingStarted from '@/components/GettingStartedSteps.tsx'
2 | import Header from '@/components/Header.tsx'
3 | import AuthenticatedNavigation from "@/components/AuthenticatedNavigation.tsx";
4 | import Logo from "@/components/Logo.tsx";
5 |
6 | export default async function Index() {
7 |
8 | return (
9 |
10 |
16 |
17 |
18 |
19 |
20 | Next steps
21 |
22 |
23 |
24 |
25 |
31 |
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/utils/supabase/server.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from '@supabase/ssr'
2 | import { cookies } from 'next/headers'
3 |
4 | export const createClient = (cookieStore: ReturnType) => {
5 | return createServerClient(
6 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
7 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
8 | {
9 | cookies: {
10 | get(name: string) {
11 | return cookieStore.get(name)?.value
12 | },
13 | set(name: string, value: string, options: CookieOptions) {
14 | try {
15 | cookieStore.set({ name, value, ...options })
16 | } catch (error) {
17 | // The `set` method was called from a Server Component.
18 | // This can be ignored if you have middleware refreshing
19 | // user sessions.
20 | }
21 | },
22 | remove(name: string, options: CookieOptions) {
23 | try {
24 | cookieStore.set({ name, value: '', ...options })
25 | } catch (error) {
26 | // The `delete` method was called from a Server Component.
27 | // This can be ignored if you have middleware refreshing
28 | // user sessions.
29 | }
30 | },
31 | },
32 | }
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import {type NextRequest, NextResponse} from 'next/server'
2 | import {createClient} from '@/utils/supabase/middleware.ts'
3 |
4 | export async function middleware(request: NextRequest) {
5 | try {
6 | // This `try/catch` block is only here for the interactive tutorial.
7 | // Feel free to remove once you have Supabase connected.
8 | const {supabase, response} = createClient(request)
9 |
10 | // Refresh session if expired - required for Server Components
11 | // https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-session-with-middleware
12 | const {data: {session}} = await supabase.auth.getSession()
13 | //
14 | if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
15 | // redirect to /login
16 | return NextResponse.redirect(new URL('/login', request.url))
17 | }
18 |
19 | return response
20 | } catch (e) {
21 | // If you are here, a Supabase client could not be created!
22 | // This is likely because you have not set up environment variables.
23 | // Check out http://localhost:3000 for Next Steps.
24 | return NextResponse.next({
25 | request: {
26 | headers: request.headers,
27 | },
28 | })
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/node.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/php.svg:
--------------------------------------------------------------------------------
1 |
2 |
5 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/blog/hello-world.mdx:
--------------------------------------------------------------------------------
1 | export const meta = {
2 | title: "Introducing Basejump",
3 | published: "2021-10-23",
4 | description: "Basejump is an opinionated NextJS starter project that includes team accounts, billing and more",
5 | category: "Announcements",
6 | }
7 |
8 | Basejump started as the first three weeks of another project I was working on. I'd decided to use it for learning [Supabase](https://supabase.com/), but very quickly realized that there aren't a lot of guides on how to
9 | leverage it for more serious projects. In particular, things like team accounts, permissions and testing are fairly poorly documented.
10 |
11 | At first, I planned on abstracting the basic SaaS setup into a starter kit and charging for it. Since then, I've learned a ton about RLS and some of the gotchas there for more complex permissions. Also, pgTAP is great once you get the hang of it, but can be pretty intimidating to get going. The end result can be some pretty poor permission logic that can lead to security bugs.
12 |
13 | As a result, I've decided instead to release Basejump as an open source project. That way more folks can benefit from some of my mistakes, contribute improvements and hopefully avoid some headache.
14 |
15 | I have additional plans for the project, but think it's reached a decent enough spot to be useful, so here it is!
16 |
17 | You can check out [Basejump on Github](https://github.com/usebasejump/basejump).
18 |
--------------------------------------------------------------------------------
/apps/basejump-docs/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{js,mjs,jsx,mdx}'],
4 | darkMode: 'class',
5 | theme: {
6 | fontSize: {
7 | '2xs': ['0.75rem', { lineHeight: '1.25rem' }],
8 | xs: ['0.8125rem', { lineHeight: '1.5rem' }],
9 | sm: ['0.875rem', { lineHeight: '1.5rem' }],
10 | base: ['1rem', { lineHeight: '1.75rem' }],
11 | lg: ['1.125rem', { lineHeight: '1.75rem' }],
12 | xl: ['1.25rem', { lineHeight: '1.75rem' }],
13 | '2xl': ['1.5rem', { lineHeight: '2rem' }],
14 | '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
15 | '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
16 | '5xl': ['3rem', { lineHeight: '1' }],
17 | '6xl': ['3.75rem', { lineHeight: '1' }],
18 | '7xl': ['4.5rem', { lineHeight: '1' }],
19 | '8xl': ['6rem', { lineHeight: '1' }],
20 | '9xl': ['8rem', { lineHeight: '1' }],
21 | },
22 | typography: require('./typography'),
23 | extend: {
24 | boxShadow: {
25 | glow: '0 0 4px rgb(0 0 0 / 0.1)',
26 | },
27 | maxWidth: {
28 | lg: '33rem',
29 | '2xl': '40rem',
30 | '3xl': '50rem',
31 | '5xl': '66rem',
32 | },
33 | opacity: {
34 | 1: '0.01',
35 | 2.5: '0.025',
36 | 7.5: '0.075',
37 | 15: '0.15',
38 | },
39 | },
40 | },
41 | plugins: [require('@tailwindcss/typography')],
42 | }
43 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/dashboard/SettingsNavigation.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import Link from "next/link"
4 | import {usePathname} from "next/navigation"
5 |
6 | import {cn} from "@/lib/utils.ts"
7 | import {buttonVariants} from "@/components/ui/button.tsx"
8 |
9 | interface SidebarNavProps extends React.HTMLAttributes {
10 | items: {
11 | href: string
12 | name: string
13 | }[]
14 | }
15 |
16 | export default function SettingsNavigation({ className, items, ...props }: SidebarNavProps) {
17 | const pathname = usePathname()
18 |
19 | return (
20 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/HeroPattern.jsx:
--------------------------------------------------------------------------------
1 | import { GridPattern } from '@/components/GridPattern'
2 |
3 | export function HeroPattern() {
4 | return (
5 |
6 |
7 |
8 |
21 |
22 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basejump-nextjs-starter",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@hookform/resolvers": "^3.3.2",
12 | "@radix-ui/react-dialog": "^1.0.5",
13 | "@radix-ui/react-dropdown-menu": "^2.0.6",
14 | "@radix-ui/react-icons": "^1.3.0",
15 | "@radix-ui/react-label": "^2.0.2",
16 | "@radix-ui/react-popover": "^1.0.7",
17 | "@radix-ui/react-select": "^2.0.0",
18 | "@radix-ui/react-separator": "^1.0.3",
19 | "@radix-ui/react-slot": "^1.0.2",
20 | "@supabase/ssr": "latest",
21 | "@supabase/supabase-js": "latest",
22 | "@usebasejump/next": "*",
23 | "autoprefixer": "10.4.15",
24 | "class-variance-authority": "^0.7.0",
25 | "clsx": "^2.0.0",
26 | "cmdk": "^0.2.0",
27 | "geist": "^1.0.0",
28 | "lucide-react": "^0.294.0",
29 | "next": "latest",
30 | "postcss": "8.4.29",
31 | "react": "18.2.0",
32 | "react-dom": "18.2.0",
33 | "react-hook-form": "^7.48.2",
34 | "tailwind-merge": "^2.1.0",
35 | "tailwindcss": "3.3.3",
36 | "tailwindcss-animate": "^1.0.7",
37 | "typescript": "5.1.3",
38 | "zod": "^3.22.4"
39 | },
40 | "devDependencies": {
41 | "@types/node": "20.3.1",
42 | "@types/react": "18.2.8",
43 | "@types/react-dom": "18.2.5",
44 | "encoding": "^0.1.13"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/dashboard/DashboardHeader.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import UserAccountButton from "@/components/basejump/UserAccountButton";
3 | import BasejumpLogo from "@/components/BasejumpLogo";
4 | import NavigatingAccountSelector from "@/app/dashboard/NavigatingAccountSelector";
5 |
6 |
7 | interface Props {
8 | accountId: string;
9 | navigation?: {
10 | name: string;
11 | href: string;
12 | }[]
13 | }
14 | export default function DashboardHeader({accountId, navigation = []}: Props) {
15 |
16 | return (
17 |
35 | )
36 |
37 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/BasejumpLogo.tsx:
--------------------------------------------------------------------------------
1 | /* istanbul ignore file */
2 | import Image from "next/image";
3 | import {cn} from "@/lib/utils";
4 |
5 | type Props = {
6 | size: "sm" | "lg";
7 | className?: string;
8 | logoOnly?: boolean;
9 | };
10 |
11 | const Logo = ({ size = "sm", className, logoOnly = false }: Props) => {
12 | const height = size === "sm" ? 40 : 150;
13 | const width = size === "sm" ? 40 : 150;
14 | return (
15 |
25 |
31 |
37 |
38 | {!logoOnly && (
39 |
45 | Basejump
46 |
)}
47 |
48 | );
49 | };
50 |
51 | export default Logo;
52 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { motion } from 'framer-motion'
3 |
4 | import { Footer } from '@/components/Footer'
5 | import { Header } from '@/components/Header'
6 | import { Logo } from '@/components/Logo'
7 | import { Navigation } from '@/components/Navigation'
8 | import { SectionProvider } from '@/components/SectionProvider'
9 |
10 | export function Layout({ children, sections = [], showFeedbackForm = false }) {
11 | return (
12 |
13 |
14 |
18 |
19 |
x
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {children}
30 |
31 |
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/BlogLayout.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { motion } from 'framer-motion'
3 |
4 | import { Footer } from '@/components/Footer'
5 | import { Header } from '@/components/Header'
6 | import { Logo } from '@/components/Logo'
7 | import { Navigation } from '@/components/Navigation'
8 | import { SectionProvider } from '@/components/SectionProvider'
9 |
10 | export function BlogLayout({ children, sections = [], showFeedbackForm = true }) {
11 | return (
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {children}
30 |
31 |
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/Code.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useState } from 'react'
4 |
5 | const CopyIcon = () => (
6 |
17 |
18 |
19 |
20 | )
21 |
22 | const CheckIcon = () => (
23 |
34 |
35 |
36 | )
37 |
38 | export default function Code({ code }: { code: string }) {
39 | const [icon, setIcon] = useState(CopyIcon)
40 |
41 | const copy = async () => {
42 | await navigator?.clipboard?.writeText(code)
43 | setIcon(CheckIcon)
44 | setTimeout(() => setIcon(CopyIcon), 2000)
45 | }
46 |
47 | return (
48 |
49 |
55 | {code}
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Icons.jsx:
--------------------------------------------------------------------------------
1 | export function TwitterIcon(props) {
2 | return (
3 |
4 |
5 |
6 | )
7 | }
8 |
9 | export function GitHubIcon(props) {
10 | return (
11 |
12 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from 'next/document'
2 |
3 | const modeScript = `
4 | let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
5 |
6 | updateMode()
7 | darkModeMediaQuery.addEventListener('change', updateModeWithoutTransitions)
8 | window.addEventListener('storage', updateModeWithoutTransitions)
9 |
10 | function updateMode() {
11 | let isSystemDarkMode = darkModeMediaQuery.matches
12 | let isDarkMode = window.localStorage.isDarkMode === 'true' || (!('isDarkMode' in window.localStorage) && isSystemDarkMode)
13 |
14 | if (isDarkMode) {
15 | document.documentElement.classList.add('dark')
16 | } else {
17 | document.documentElement.classList.remove('dark')
18 | }
19 |
20 | if (isDarkMode === isSystemDarkMode) {
21 | delete window.localStorage.isDarkMode
22 | }
23 | }
24 |
25 | function disableTransitionsTemporarily() {
26 | document.documentElement.classList.add('[&_*]:!transition-none')
27 | window.setTimeout(() => {
28 | document.documentElement.classList.remove('[&_*]:!transition-none')
29 | }, 0)
30 | }
31 |
32 | function updateModeWithoutTransitions() {
33 | disableTransitionsTemporarily()
34 | updateMode()
35 | }
36 | `
37 |
38 | export default function Document() {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Tag.jsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx'
2 |
3 | const variantStyles = {
4 | medium: 'rounded-lg px-1.5 ring-1 ring-inset',
5 | }
6 |
7 | const colorStyles = {
8 | emerald: {
9 | small: 'text-emerald-500 dark:text-emerald-400',
10 | medium:
11 | 'ring-emerald-300 dark:ring-emerald-400/30 bg-emerald-400/10 text-emerald-500 dark:text-emerald-400',
12 | },
13 | sky: {
14 | small: 'text-sky-500',
15 | medium:
16 | 'ring-sky-300 bg-sky-400/10 text-sky-500 dark:ring-sky-400/30 dark:bg-sky-400/10 dark:text-sky-400',
17 | },
18 | amber: {
19 | small: 'text-amber-500',
20 | medium:
21 | 'ring-amber-300 bg-amber-400/10 text-amber-500 dark:ring-amber-400/30 dark:bg-amber-400/10 dark:text-amber-400',
22 | },
23 | rose: {
24 | small: 'text-red-500 dark:text-rose-500',
25 | medium:
26 | 'ring-rose-200 bg-rose-50 text-red-500 dark:ring-rose-500/20 dark:bg-rose-400/10 dark:text-rose-400',
27 | },
28 | zinc: {
29 | small: 'text-zinc-400 dark:text-zinc-500',
30 | medium:
31 | 'ring-zinc-200 bg-zinc-50 text-zinc-500 dark:ring-zinc-500/20 dark:bg-zinc-400/10 dark:text-zinc-400',
32 | },
33 | }
34 |
35 | const valueColorMap = {
36 | get: 'emerald',
37 | post: 'sky',
38 | put: 'amber',
39 | delete: 'rose',
40 | }
41 |
42 | export function Tag({
43 | children,
44 | variant = 'medium',
45 | color = valueColorMap[children.toLowerCase()] ?? 'emerald',
46 | }) {
47 | return (
48 |
55 | {children}
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/apps/basejump-docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basejump-docs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "browserslist": "defaults, not ie <= 11",
12 | "dependencies": {
13 | "@algolia/autocomplete-core": "^1.7.3",
14 | "@headlessui/react": "^1.7.13",
15 | "@mdx-js/loader": "^2.1.5",
16 | "@mdx-js/react": "^2.1.5",
17 | "@next/mdx": "^13.0.3",
18 | "@sindresorhus/slugify": "^2.1.1",
19 | "@tailwindcss/typography": "^0.5.8",
20 | "@vercel/og": "^0.5.11",
21 | "acorn": "^8.8.1",
22 | "autoprefixer": "^10.4.7",
23 | "clsx": "^1.2.0",
24 | "fast-glob": "^3.2.12",
25 | "flexsearch": "^0.7.31",
26 | "focus-visible": "^5.2.0",
27 | "framer-motion": "7.8.1",
28 | "gray-matter": "^4.0.3",
29 | "lucide-react": "^0.268.0",
30 | "mdast-util-to-string": "^3.2.0",
31 | "mdx-annotations": "^0.1.1",
32 | "next": "13.4.2",
33 | "postcss-focus-visible": "^6.0.4",
34 | "react": "18.2.0",
35 | "react-dom": "18.2.0",
36 | "react-highlight-words": "^0.20.0",
37 | "recma-nextjs-static-props": "^1.0.0",
38 | "rehype-mdx-title": "^2.0.0",
39 | "remark": "^14.0.2",
40 | "remark-gfm": "^3.0.1",
41 | "remark-mdx": "^2.3.0",
42 | "shiki": "^0.11.1",
43 | "simple-functional-loader": "^1.2.1",
44 | "tailwindcss": "^3.3.0",
45 | "unist-util-filter": "^4.0.1",
46 | "unist-util-visit": "^4.1.1",
47 | "zustand": "^4.3.2"
48 | },
49 | "devDependencies": {
50 | "eslint": "8.26.0",
51 | "eslint-config-next": "13.0.2",
52 | "prettier": "^2.8.7",
53 | "prettier-plugin-tailwindcss": "^0.2.6"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Guides.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button'
2 | import { Heading } from '@/components/Heading'
3 |
4 | const guides = [
5 | {
6 | href: '/docs/installation',
7 | name: 'Installation',
8 | description: 'Learn how to get started with Basejump.',
9 | },
10 | {
11 | href: '/docs/rls',
12 | name: 'Permissions',
13 | description: 'Understand how to extend your Supabase project with new protected tables.',
14 | },
15 | {
16 | href: '/docs/testing',
17 | name: 'Testing',
18 | description:
19 | 'Learn how to test your Supabase and Basejump project using pgTAP.',
20 | },
21 | {
22 | href: '/docs/deployment',
23 | name: 'Deploying',
24 | description:
25 | 'Learn how to go to production with your new Basejump project.',
26 | },
27 | ]
28 |
29 | export function Guides() {
30 | return (
31 |
32 |
33 | Guides
34 |
35 |
36 | {guides.map((guide) => (
37 |
38 |
39 | {guide.name}
40 |
41 |
42 | {guide.description}
43 |
44 |
45 |
48 |
49 |
50 | ))}
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/CodeExamples.mdx:
--------------------------------------------------------------------------------
1 | import {CodeGroup} from "@/components/mdx.jsx";
2 |
3 |
4 |
5 |
6 | ```js
7 | import { createClient } from '@supabase/supabase-js'
8 |
9 | const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
10 |
11 | const {data, error} = await supabase.rpc('create_account', {
12 | slug: 'team-slug',
13 | name: 'Your Team Name',
14 | });
15 | ```
16 |
17 | ```python
18 | from supabase import create_client
19 |
20 | supabase = create_client(SUPABASE_URL, SUPABASE_ANON_KEY)
21 | response = supabase.rpc(fn="create_account", params={
22 | "slug": "team-slug",
23 | "name": "your team"
24 | }).execute()
25 | ```
26 |
27 | ```swift
28 | let client = SupabaseClient(supabaseURL: "{ SUPABASE_URL }", supabaseKey: "{ SUPABASE_ANON_KEY }")
29 |
30 | struct CreateAccountParams: Codable {
31 | let slug: String
32 | let name: String
33 | }
34 |
35 | let query = client.database.rpc(
36 | fn: "create_account",
37 | params: CreateAccountParams(slug: "team-slug", name: "Team Name")
38 | )
39 |
40 | Task {
41 | do {
42 | let response: [DataModel] = try await query.execute().value
43 | print("### Returned: \(response)")
44 | } catch {
45 | print("### RPC Error: \(error)")
46 | }
47 | }
48 | ```
49 |
50 | ```bash {{title: 'cURL'}}
51 | curl -G https://YOUR_INSTANCE.com/rest/v1/rpc/create_account \
52 | -d '{"slug": "team-slug", name: "Team Name"}' \
53 | -H "Content-Type: application/json" \
54 | -H "Authorization: Bearer SUPABASE_KEY" \
55 | -H "apikey: SUPABASE_KEY" \
56 | ```
57 |
58 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/blog/index.jsx:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs'
2 | import Link from 'next/link'
3 | import { HeroPattern } from '@/components/HeroPattern.jsx'
4 | import Head from "next/head";
5 |
6 | const BlogIndex = ({ posts }) => {
7 | return (
8 |
9 |
10 | {posts.map((post) => (
11 |
16 |
{post.meta.category}
17 |
{post.meta.title}
18 |
{post.meta.description}
19 |
20 | ))}
21 |
22 | )
23 | }
24 |
25 | export default BlogIndex
26 |
27 | export async function getStaticProps() {
28 | // load file paths from current directory that end in `.mdx`. Ignore all other file extensions
29 | const postFilePaths = fs.readdirSync('./src/pages/blog')
30 |
31 | const posts = []
32 |
33 | for (const postFilePath of postFilePaths) {
34 | if (!postFilePath.endsWith('.mdx')) continue
35 |
36 | const file = await import(`./${postFilePath}`)
37 | const {
38 | props: { meta },
39 | } = file.getStaticProps()
40 | const slug = postFilePath.replace(/\.mdx?$/, '')
41 | posts.push({
42 | meta: {
43 | ...meta,
44 | slug,
45 | },
46 | })
47 | }
48 | return {
49 | props: {
50 | title: 'Articles - Basejump',
51 | description: 'Articles about Basejump, Supabase, and SaaS development',
52 | posts: posts.sort((a, b) => {
53 | return new Date(b.meta.published) - new Date(a.meta.published)
54 | }),
55 | },
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/go.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/deployment.mdx:
--------------------------------------------------------------------------------
1 | export const description = "Learn how to deploy your supabase project"
2 |
3 | # Deploying Basejump
4 |
5 | Once you're ready to go to production, deploying to your Supabase account is easy. This guide assumes that you've already [installed the Supabase CLI](https://supabase.com/images/docs/guides/cli)
6 |
7 | ## Setup your account
8 |
9 | Head to [Supabase](https://supabase.com) and create a new project. Make sure you record your database password somewhere secure, you'll need it later.
10 | 
11 |
12 | ## Setup the Supabase CLI
13 | In general, you can follow the [official guide](https://supabase.com/images/docs/guides/cli/local-development#deploy-your-project) for up to date info.
14 |
15 | ### Login to Supabase
16 | ```bash
17 | supabase login
18 | ```
19 |
20 | ### Link your project
21 | You can get your project ID on the Settings page of your project. To finish this, you'll need the database password you input when creating your project.
22 | ```bash
23 | supabase link --project-ref
24 | ```
25 |
26 | ## Run your migrations
27 | You'll need your database password for this step too.
28 | ```bash
29 | supabase db push
30 | ```
31 |
32 | ## Deploy your edge functions
33 | The billing portion of Basejump is handled through two edge functions - one for general billing lookups and one for handling provider webhooks.
34 |
35 | To deploy them, you'll first add your secrets to production
36 |
37 | ```bash
38 | supabase secrets set STRIPE_API_KEY=
39 | supabase secrets set STRIPE_WEBHOOK_SIGNING_SECRET=
40 | ```
41 |
42 | Then you can deploy the functions
43 | ```bash
44 | supabase functions deploy billing-functions
45 | supabase functions deploy billing-webhooks --no-verify-jwt
46 | ```
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/ContentLayout.jsx:
--------------------------------------------------------------------------------
1 | import { ContentHeader } from '@/components/ContentHeader.jsx'
2 | import { ContentFooter } from '@/components/ContentFooter.jsx'
3 | import { SectionProvider } from '@/components/SectionProvider.jsx'
4 | import { motion } from 'framer-motion'
5 | import { Button } from '@/components/Button.jsx'
6 |
7 | export function ContentLayout({ meta, children, sections = [] }) {
8 | return (
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {!!meta?.title && (
22 |
23 |
24 |
27 | /
28 |
31 | /
32 | {meta.title}
33 |
34 |
35 |
{meta.category}
36 |
{meta.title}
37 |
38 |
39 | )}
40 | {children}
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/utils/supabase/middleware.ts:
--------------------------------------------------------------------------------
1 | import { createServerClient, type CookieOptions } from '@supabase/ssr'
2 | import { type NextRequest, NextResponse } from 'next/server'
3 |
4 | export const createClient = (request: NextRequest) => {
5 | // Create an unmodified response
6 | let response = NextResponse.next({
7 | request: {
8 | headers: request.headers,
9 | },
10 | })
11 |
12 | const supabase = createServerClient(
13 | process.env.NEXT_PUBLIC_SUPABASE_URL!,
14 | process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
15 | {
16 | cookies: {
17 | get(name: string) {
18 | return request.cookies.get(name)?.value
19 | },
20 | set(name: string, value: string, options: CookieOptions) {
21 | // If the cookie is updated, update the cookies for the request and response
22 | request.cookies.set({
23 | name,
24 | value,
25 | ...options,
26 | })
27 | response = NextResponse.next({
28 | request: {
29 | headers: request.headers,
30 | },
31 | })
32 | response.cookies.set({
33 | name,
34 | value,
35 | ...options,
36 | })
37 | },
38 | remove(name: string, options: CookieOptions) {
39 | // If the cookie is removed, update the cookies for the request and response
40 | request.cookies.set({
41 | name,
42 | value: '',
43 | ...options,
44 | })
45 | response = NextResponse.next({
46 | request: {
47 | headers: request.headers,
48 | },
49 | })
50 | response.cookies.set({
51 | name,
52 | value: '',
53 | ...options,
54 | })
55 | },
56 | },
57 | }
58 | )
59 |
60 | return { supabase, response }
61 | }
62 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/ModeToggle.jsx:
--------------------------------------------------------------------------------
1 | function SunIcon(props) {
2 | return (
3 |
4 |
5 |
9 |
10 | )
11 | }
12 |
13 | function MoonIcon(props) {
14 | return (
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export function ModeToggle() {
22 | function disableTransitionsTemporarily() {
23 | document.documentElement.classList.add('[&_*]:!transition-none')
24 | window.setTimeout(() => {
25 | document.documentElement.classList.remove('[&_*]:!transition-none')
26 | }, 0)
27 | }
28 |
29 | function toggleMode() {
30 | disableTransitionsTemporarily()
31 |
32 | let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
33 | let isSystemDarkMode = darkModeMediaQuery.matches
34 | let isDarkMode = document.documentElement.classList.toggle('dark')
35 |
36 | if (isDarkMode === isSystemDarkMode) {
37 | delete window.localStorage.isDarkMode
38 | } else {
39 | window.localStorage.isDarkMode = isDarkMode
40 | }
41 | }
42 |
43 | return (
44 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/supabase/migrations/20231208070356_install-basejump.sql:
--------------------------------------------------------------------------------
1 | /*---------------------
2 | ---- install dbdev ----
3 | ----------------------
4 | Requires:
5 | - pg_tle: https://github.com/aws/pg_tle
6 | - pgsql-http: https://github.com/pramsey/pgsql-http
7 | */
8 | create extension if not exists http with schema extensions;
9 | create extension if not exists pg_tle;
10 | select pgtle.uninstall_extension_if_exists('supabase-dbdev');
11 | drop extension if exists "supabase-dbdev";
12 | select
13 | pgtle.install_extension(
14 | 'supabase-dbdev',
15 | resp.contents ->> 'version',
16 | 'PostgreSQL package manager',
17 | resp.contents ->> 'sql'
18 | )
19 | from http(
20 | (
21 | 'GET',
22 | 'https://api.database.dev/rest/v1/'
23 | || 'package_versions?select=sql,version'
24 | || '&package_name=eq.supabase-dbdev'
25 | || '&order=version.desc'
26 | || '&limit=1',
27 | array[
28 | (
29 | 'apiKey',
30 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJp'
31 | || 'c3MiOiJzdXBhYmFzZSIsInJlZiI6InhtdXB0cHBsZnZpaWZyY'
32 | || 'ndtbXR2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE2ODAxMDczNzI'
33 | || 'sImV4cCI6MTk5NTY4MzM3Mn0.z2CN0mvO2No8wSi46Gw59DFGCTJ'
34 | || 'rzM0AQKsu_5k134s'
35 | )::http_header
36 | ],
37 | null,
38 | null
39 | )
40 | ) x,
41 | lateral (
42 | select
43 | ((row_to_json(x) -> 'content') #>> '{}')::json -> 0
44 | ) resp(contents);
45 | create extension "supabase-dbdev";
46 | select dbdev.install('supabase-dbdev');
47 | drop extension if exists "supabase-dbdev";
48 | create extension "supabase-dbdev";
49 |
50 |
51 | select dbdev.install('basejump-basejump_core');
52 |
53 | create extension "basejump-basejump_core"
54 | version '2.0.1';
55 |
56 | UPDATE basejump.config SET enable_team_accounts = TRUE;
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/index.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/Button.jsx'
2 | import { HeroPattern } from '@/components/HeroPattern.jsx'
3 | import HomepageFeatures from '@/components/homepage/Features.jsx'
4 | import HomepageUiComponents from '@/components/homepage/UiComponents.jsx'
5 | import HomepageCrossPlatform from '@/components/homepage/CrossPlatform.jsx'
6 | import HomepageBuildWithSupabase from '@/components/homepage/BuildWithSupabase.jsx'
7 | import Link from 'next/link'
8 | import {Note} from "@/components/mdx"
9 |
10 | const IndexPage = () => (
11 | <>
12 |
13 |
14 |
15 |
16 |
17 | Add auth, teams and billing to your Supabase app in 31 seconds
18 |
19 |
Free and open source
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | >
40 | )
41 |
42 | export default IndexPage
43 |
44 | export async function getStaticProps() {
45 | return { props: { title: "Open source Supabase SaaS starter", description: "Launch your Supabase SaaS app faster with Basejump. Free and open source" } }
46 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 222.2 84% 4.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 222.2 84% 4.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 222.2 84% 4.9%;
15 |
16 | --primary: 222.2 47.4% 11.2%;
17 | --primary-foreground: 210 40% 98%;
18 |
19 | --secondary: 210 40% 96.1%;
20 | --secondary-foreground: 222.2 47.4% 11.2%;
21 |
22 | --muted: 210 40% 96.1%;
23 | --muted-foreground: 215.4 16.3% 46.9%;
24 |
25 | --accent: 210 40% 96.1%;
26 | --accent-foreground: 222.2 47.4% 11.2%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 210 40% 98%;
30 |
31 | --border: 214.3 31.8% 91.4%;
32 | --input: 214.3 31.8% 91.4%;
33 | --ring: 222.2 84% 4.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 222.2 84% 4.9%;
40 | --foreground: 210 40% 98%;
41 |
42 | --card: 222.2 84% 4.9%;
43 | --card-foreground: 210 40% 98%;
44 |
45 | --popover: 222.2 84% 4.9%;
46 | --popover-foreground: 210 40% 98%;
47 |
48 | --primary: 210 40% 98%;
49 | --primary-foreground: 222.2 47.4% 11.2%;
50 |
51 | --secondary: 217.2 32.6% 17.5%;
52 | --secondary-foreground: 210 40% 98%;
53 |
54 | --muted: 217.2 32.6% 17.5%;
55 | --muted-foreground: 215 20.2% 65.1%;
56 |
57 | --accent: 217.2 32.6% 17.5%;
58 | --accent-foreground: 210 40% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 210 40% 98%;
62 |
63 | --border: 217.2 32.6% 17.5%;
64 | --input: 217.2 32.6% 17.5%;
65 | --ring: 212.7 26.8% 83.9%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | }
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/CrossPlatform.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Col } from '@/components/mdx.jsx'
2 | import CodeExample from './CodeExamples.mdx'
3 | import logoJS from '@/images/logos/javascript.svg'
4 | import logoTS from '@/images/logos/typescript.svg'
5 | import logoPython from '@/images/logos/python.svg'
6 | import logoSwift from '@/images/logos/swift.svg'
7 | import Image from 'next/image'
8 |
9 | export default function HomepageCrossPlatform() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | Build for any platform
18 |
19 |
20 | Leverage Supabase libraries in Javascript, Python, Go, Swift or
21 | anything else.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | )
48 | }
49 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/icons/CogIcon.jsx:
--------------------------------------------------------------------------------
1 | export function CogIcon(props) {
2 | return (
3 |
4 |
10 |
16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/understanding-accounts.mdx:
--------------------------------------------------------------------------------
1 | export const description =
2 | 'Understanding personal and team accounts in Basejump'
3 |
4 | # Understanding accounts and roles
5 |
6 | Out of the box, we support personal accounts and team accounts.
7 |
8 | A good example of this in the wild is Github, where you have your own personal account, but can also create team accounts.
9 |
10 | ## Personal Accounts
11 | * Created automatically when a user signs up, using the user's ID as the Account ID
12 | * Do NOT support invitations or multiple members
13 | * DO support billing through Stripe
14 |
15 | **Personal accounts cannot be disabled**, they are always created. You can choose to ignore them by simply not creating any interfaces for them and disabling billing for them.
16 |
17 |
18 | ## Team Accounts
19 | * Any user can create a team
20 | * DO support billing through Stripe
21 | * DO support invitations and multiple members
22 | * The user who creates the account becomes the `primary_owner`
23 | * `owner` users can manage invitations, users and billing
24 | * `member` have access to accounts, but can not edit them
25 | * `primary_owner` cannot be removed, but you can change the primary owner of an account
26 |
27 | To enable team accounts, set `team_accounts_enabled` to true in the `basejump.config` table.
28 |
29 | ## Team member roles
30 | Basejump provides a set of roles that you can use to configure your permissions. You can also create your own roles if you need to.
31 |
32 | * `owner` - Owners have access to everything, including billing and inviting new users
33 | * `member` - Members can access the account, but cannot invite new users or manage billing
34 |
35 | ### Adding a new role
36 | To add a new role, you need to add it to the `basejump.account_roles` enum value in postgres. New roles do not have any special permissions by default, any RLS policies defined using basic account member checks will apply to the new role.
37 |
38 | ```sql
39 | ALTER TYPE basejump.account_role ADD VALUE 'your_new_role';
40 | ```
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/python-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/Features.jsx:
--------------------------------------------------------------------------------
1 | export default function HomepageFeatures() {
2 | return (
3 |
4 |
5 |
6 |
Accounts & teams
7 |
8 | Authentication, personal accounts, team accounts and member
9 | permissions
10 |
11 |
12 |
13 |
Subscription billing
14 |
15 | Manage account subscriptions out of the box using Stripe. Add new
16 | providers easily
17 |
18 |
19 |
20 |
Optional React components
21 |
22 | Launch even faster using customizable React UI components for
23 | account functionality
24 |
25 |
26 |
27 |
Powered by Supabase
28 |
29 | Use as a standalone auth/billing system or as part of your existing
30 | Supabase app
31 |
32 |
33 |
34 |
Extensible roles & permissions
35 |
36 | Leverage Supabase RLS policies to restrict access to data by user
37 | role
38 |
39 |
40 |
41 |
It's all your data
42 |
43 | Everything is stored in your own Supabase database. Customize
44 | everything and extend with your own tables
45 |
46 |
47 |
48 |
49 | )
50 | }
51 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Button.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import clsx from 'clsx'
3 |
4 | function ArrowIcon(props) {
5 | return (
6 |
7 |
13 |
14 | )
15 | }
16 |
17 | const variantStyles = {
18 | primary:
19 | 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-400/10 dark:text-emerald-400 dark:ring-1 dark:ring-inset dark:ring-emerald-400/20 dark:hover:bg-emerald-400/10 dark:hover:text-emerald-300 dark:hover:ring-emerald-300',
20 | secondary:
21 | 'rounded-full bg-zinc-100 py-1 px-3 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300',
22 | filled:
23 | 'rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-emerald-500 dark:text-white dark:hover:bg-emerald-400',
24 | outline:
25 | 'rounded-full py-1 px-3 text-zinc-700 ring-1 ring-inset ring-zinc-900/10 hover:bg-zinc-900/2.5 hover:text-zinc-900 dark:text-zinc-400 dark:ring-white/10 dark:hover:bg-white/5 dark:hover:text-white',
26 | text: 'text-emerald-500 hover:text-emerald-600 dark:text-emerald-400 dark:hover:text-emerald-500',
27 | }
28 |
29 | export function Button({
30 | variant = 'primary',
31 | className,
32 | children,
33 | arrow,
34 | ...props
35 | }) {
36 | let Component = props.href ? Link : 'button'
37 |
38 | className = clsx(
39 | 'inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition',
40 | variantStyles[variant],
41 | className
42 | )
43 |
44 | let arrowIcon = (
45 |
53 | )
54 |
55 | return (
56 |
57 | {arrow === 'left' && arrowIcon}
58 | {children}
59 | {arrow === 'right' && arrowIcon}
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/swift.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import NextLogo from './NextLogo.tsx'
2 | import SupabaseLogo from './SupabaseLogo.tsx'
3 | import BasejumpLogo from "./BasejumpLogo.tsx";
4 |
5 | export default function Header() {
6 | return (
7 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/UiComponents.jsx:
--------------------------------------------------------------------------------
1 | import {Button} from '@/components/mdx.jsx'
2 | import Link from 'next/link'
3 |
4 | export default function HomepageUiComponents() {
5 | return (
6 |
7 |
8 |
9 |

13 |
14 |
15 |
16 |
17 |
18 | Ready to go React components
19 |
20 |
21 | Launch faster with pre-built React components for teams, users and billing. Built on shadcn, so you can customize them to your hearts content.
22 |
23 |
24 | React components are currently a work in progress. Feedback welcome!
25 |
26 |
27 |
28 |
29 |
Personal and team functionality
30 |
31 | Billing portal, team management and invitations. All ready to
32 | go.
33 |
34 |
35 |
36 |
Fully customizable
37 |
38 | Built on tailwind and shadcn, you have full control over the look and feel of all components.
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/homepage/BuildWithSupabase.jsx:
--------------------------------------------------------------------------------
1 | import { Button, Col } from '@/components/mdx.jsx'
2 | import SupabaseExamples from './SupabaseExamples.mdx'
3 |
4 | export default function HomepageBuildWithSupabase() {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Keep building with Supabase
18 |
19 | ...or not
20 |
21 |
22 | Already using Supabase? Add Basejump to your
23 | existing project with a single migration file
24 |
25 |
26 | Not sure if you want to use Supabase at all? No
27 | problem, you can launch Basejump with a free Supabase instance
28 | and use it as a headless authentication API
29 |
30 |
31 |
32 |
33 |
FREE
34 |
Supabase is free to use for most projects
35 |
36 |
37 |
Open Source
38 |
39 | Supabase is open source. Host it with them or on your own.
40 |
41 |
42 |
43 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | )
62 | }
63 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/lib/get-navigation.js:
--------------------------------------------------------------------------------
1 | const docsNavigation = [
2 | {
3 | title: 'Guides',
4 | links: [
5 | {title: 'Getting started', href: '/docs'},
6 | {title: 'Installation', href: '/docs/installation'},
7 | {
8 | title: 'Understanding accounts & roles',
9 | href: '/docs/understanding-accounts',
10 | },
11 | {title: 'Using RLS & permissions', href: '/docs/rls'},
12 | {title: 'Adding protected tables', href: '/docs/example-schema'},
13 |
14 | {title: 'Testing & pgTAP', href: '/docs/testing'},
15 | {title: 'Deploying', href: '/docs/deployment'},
16 | ],
17 | },
18 | {
19 | title: 'UI Components',
20 | links: [{title: 'React / Next.js', href: '/docs/react'}],
21 | },
22 | {
23 | title: 'API Reference',
24 | links: [
25 | {title: 'Accounts', href: '/docs/accounts'},
26 | {title: 'Invitations', href: '/docs/invitations'},
27 | {title: 'Team Members', href: '/docs/team-members'},
28 | {title: 'Permissions', href: '/docs/permissions'},
29 | {title: 'Billing', href: '/docs/billing'},
30 | {title: 'Database Functions', href: '/docs/database-utilities'},
31 | ],
32 | },
33 | {
34 | title: 'Billing Providers',
35 | links: [
36 | {title: 'Stripe', href: '/docs/billing-stripe'},
37 | {title: 'Add new provider', href: '/docs/billing-new'},
38 | ]
39 | },
40 | ];
41 |
42 | const reactNavigation = [
43 | {
44 | title: 'Getting Started',
45 | links: [
46 | {title: 'Installation', href: '/docs/react'},
47 | ],
48 | },
49 | {
50 | title: 'Utilities / Hooks',
51 | links: [
52 | {title: 'SignedIn / SignedOut', href: '/docs/react/user-session'},
53 | {title: 'Data loading hooks', href: '/docs/react/hooks'},
54 | ],
55 | },
56 | {
57 | title: "UI Components",
58 | links: [
59 | {title: 'UserAccountButton', href: '/docs/react/user-account-button'},
60 | {title: 'AccountSelector', href: '/docs/react/account-selector'},
61 | {title: 'NewTeamForm', href: '/docs/react/new-team-form'},
62 | {title: 'AcceptInvitationForm', href: '/docs/react/accept-invitation-form'},
63 | {title: 'InviteMemberForm', href: '/docs/react/invite-member-form'},
64 | ]
65 | }
66 | ];
67 |
68 | export default function getNavigation(urlPath = '/docs') {
69 | if (urlPath.startsWith('/docs/react')) {
70 | return reactNavigation;
71 | }
72 |
73 | return docsNavigation;
74 | }
--------------------------------------------------------------------------------
/apps/nextjs-starter/src/components/basejump/UserAccountButton.tsx:
--------------------------------------------------------------------------------
1 | import {Button} from "@/components/ui/button"
2 | import {
3 | DropdownMenu,
4 | DropdownMenuContent,
5 | DropdownMenuGroup,
6 | DropdownMenuItem,
7 | DropdownMenuLabel,
8 | DropdownMenuSeparator,
9 | DropdownMenuTrigger,
10 | } from "@/components/ui/dropdown-menu"
11 | import Link from "next/link";
12 | import {UserIcon} from "lucide-react";
13 | import {createClient} from "@/utils/supabase/server.ts";
14 | import {redirect} from "next/navigation";
15 | import {cookies} from "next/headers";
16 |
17 | export default async function UserAccountButton() {
18 | const cookieStore = cookies();
19 | const supabaseClient = createClient(cookieStore);
20 | const {data: personalAccount} = await supabaseClient.rpc('get_personal_account');
21 |
22 | const signOut = async () => {
23 | 'use server'
24 |
25 | const cookieStore = cookies()
26 | const supabase = createClient(cookieStore)
27 | await supabase.auth.signOut()
28 | return redirect('/login')
29 | }
30 |
31 | return (
32 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
{personalAccount.name}
42 |
43 | {personalAccount.email}
44 |
45 |
46 |
47 |
48 |
49 |
50 | Account
51 |
52 |
53 | Settings
54 |
55 |
56 | Teams
57 |
58 |
59 |
60 |
61 |
64 |
65 |
66 |
67 | )
68 | }
69 |
--------------------------------------------------------------------------------
/packages/shared/src/types/api.ts:
--------------------------------------------------------------------------------
1 | import {Database} from "./basejump-types";
2 |
3 | export type CurrentUserAccountRoleResponse = {
4 | account_role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
5 | is_primary_owner: boolean;
6 | is_personal_account: boolean;
7 | };
8 |
9 | export type GetBillingPlansResponse = Array<{
10 | id: string;
11 | name: string;
12 | description?: string;
13 | amount: number;
14 | currency: string;
15 | interval: "month" | "year" | "one_time";
16 | interval_count: 1;
17 | trial_period_days?: 30;
18 | active?: boolean;
19 | metadata?: {
20 | [key: string]: string;
21 | };
22 | }>;
23 |
24 | export type GetAccountResponse = {
25 | account_id: string;
26 | role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
27 | is_primary_owner: boolean;
28 | name: string;
29 | slug: string;
30 | personal_account: boolean;
31 | created_at: Date;
32 | updated_at: Date;
33 | metadata: {
34 | [key: string]: any;
35 | };
36 | };
37 |
38 | export type CreateAccountResponse = GetAccountResponse;
39 | export type UpdateAccountResponse = GetAccountResponse;
40 |
41 | export type GetAccountsResponse = {
42 | account_id: string;
43 | role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
44 | is_primary_owner: boolean;
45 | name: string;
46 | slug: string;
47 | personal_account: boolean;
48 | created_at: Date;
49 | updated_at: Date;
50 | }[];
51 |
52 | export type GetAccountMembersResponse = {
53 | user_id: string;
54 | name: string;
55 | account_role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
56 | is_primary_owner: boolean;
57 | }[];
58 |
59 | export type GetAccountInvitesResponse = {
60 | account_role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
61 | invitation_type: Database["basejump"]["Tables"]["invitations"]["Row"]["invitation_type"];
62 | created_at: Date;
63 | }[];
64 |
65 | export type CreateInvitationResponse = {
66 | token: string;
67 | };
68 |
69 | export type GetAccountBillingStatusResponse = {
70 | subscription_id: string;
71 | subscription_active: boolean;
72 | status: Database["basejump"]["Tables"]["billing_subscriptions"]["Row"]["status"];
73 | billing_email?: string;
74 | account_role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
75 | is_primary_owner: boolean;
76 | billing_enabled: boolean;
77 | };
78 |
79 | export type AcceptInvitationResponse = {
80 | account_id: string;
81 | account_role: Database["basejump"]["Tables"]["account_user"]["Row"]["account_role"];
82 | slug: string;
83 | };
84 |
85 | export type LookupInvitationResponse = {
86 | active: boolean;
87 | account_name: string;
88 | };
89 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/api/og.jsx:
--------------------------------------------------------------------------------
1 | import { ImageResponse } from "@vercel/og";
2 |
3 | export const config = {
4 | runtime: "experimental-edge",
5 | };
6 |
7 | export default function (req) {
8 | try {
9 | const { searchParams } = new URL(req.url);
10 |
11 | const hasTitle = searchParams.has("title");
12 | const title = hasTitle
13 | ? searchParams.get("title")?.slice(0, 100)
14 | : "Basejump";
15 | return new ImageResponse(
16 | (
17 |
33 | {hasTitle && (
34 |
39 | {title}
40 |
41 | )}
42 |
52 |

61 | Basejump
62 |
63 |
64 | ),
65 | {
66 | width: 1200,
67 | height: 600,
68 | }
69 | );
70 | } catch (e) {
71 | console.log(`${e.message}`);
72 | return new Response(`Failed to generate an image`, {
73 | status: 500,
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/ContentHeader.jsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react'
2 | import Link from 'next/link'
3 | import clsx from 'clsx'
4 | import { motion, useScroll, useTransform } from 'framer-motion'
5 | import { Logo } from '@/components/Logo'
6 | import {
7 | MobileNavigation,
8 | useIsInsideMobileNavigation,
9 | useMobileNavigationStore,
10 | } from '@/components/MobileNavigation'
11 | import { ModeToggle } from '@/components/ModeToggle'
12 |
13 | function TopLevelNavItem({ href, children }) {
14 | return (
15 |
16 |
20 | {children}
21 |
22 |
23 | )
24 | }
25 |
26 | export const ContentHeader = forwardRef(function Header({ className }, ref) {
27 | let { isOpen: mobileNavIsOpen } = useMobileNavigationStore()
28 | let isInsideMobileNavigation = useIsInsideMobileNavigation()
29 |
30 | let { scrollY } = useScroll()
31 | let bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9])
32 | let bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8])
33 |
34 | return (
35 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | })
79 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import { Router, useRouter } from 'next/router'
3 | import { MDXProvider } from '@mdx-js/react'
4 |
5 | import { Layout } from '@/components/Layout'
6 | import * as mdxComponents from '@/components/mdx'
7 | import { useMobileNavigationStore } from '@/components/MobileNavigation'
8 | import getFullUrl from "@/lib/get-full-url";
9 |
10 | import '@/styles/tailwind.css'
11 | import 'focus-visible'
12 | import { ContentLayout } from '@/components/ContentLayout.jsx'
13 |
14 | function onRouteChange() {
15 | useMobileNavigationStore.getState().close()
16 | }
17 |
18 | Router.events.on('routeChangeStart', onRouteChange)
19 | Router.events.on('hashChangeStart', onRouteChange)
20 |
21 | export default function App({ Component, pageProps }) {
22 | const router = useRouter()
23 | const isDocsPath = router.pathname.startsWith('/docs');
24 |
25 | return (
26 | <>
27 |
28 | {(!!pageProps.title || !!pageProps.meta?.title) && (
29 | <>
30 | {`${pageProps.title || pageProps.meta?.title} - Basejump`}
31 |
32 |
33 |
34 |
38 | >
39 | )}
40 | {(!!pageProps.description && !!pageProps.meta?.description) && (
41 | <>
42 |
46 |
50 |
54 | >
55 | )}
56 |
61 |
62 |
63 | {isDocsPath ? (
64 |
65 |
66 |
67 | ) : (
68 |
69 |
70 |
71 | )}
72 |
73 | >
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/apps/nextjs-starter/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const {fontFamily} = require("tailwindcss/defaultTheme")
2 |
3 | /** @type {import('tailwindcss').Config} */
4 | module.exports = {
5 | darkMode: ["class"],
6 | content: ["src/app/**/*.{ts,tsx}", "src/components/**/*.{ts,tsx}"],
7 | theme: {
8 | container: {
9 | center: true,
10 | padding: "2rem",
11 | screens: {
12 | "2xl": "1400px",
13 | },
14 | },
15 | extend: {
16 | colors: {
17 | border: "hsl(var(--border))",
18 | input: "hsl(var(--input))",
19 | ring: "hsl(var(--ring))",
20 | background: "hsl(var(--background))",
21 | foreground: "hsl(var(--foreground))",
22 | primary: {
23 | DEFAULT: "hsl(var(--primary))",
24 | foreground: "hsl(var(--primary-foreground))",
25 | },
26 | secondary: {
27 | DEFAULT: "hsl(var(--secondary))",
28 | foreground: "hsl(var(--secondary-foreground))",
29 | },
30 | destructive: {
31 | DEFAULT: "hsl(var(--destructive))",
32 | foreground: "hsl(var(--destructive-foreground))",
33 | },
34 | muted: {
35 | DEFAULT: "hsl(var(--muted))",
36 | foreground: "hsl(var(--muted-foreground))",
37 | },
38 | accent: {
39 | DEFAULT: "hsl(var(--accent))",
40 | foreground: "hsl(var(--accent-foreground))",
41 | },
42 | popover: {
43 | DEFAULT: "hsl(var(--popover))",
44 | foreground: "hsl(var(--popover-foreground))",
45 | },
46 | card: {
47 | DEFAULT: "hsl(var(--card))",
48 | foreground: "hsl(var(--card-foreground))",
49 | },
50 | },
51 | borderRadius: {
52 | lg: `var(--radius)`,
53 | md: `calc(var(--radius) - 2px)`,
54 | sm: "calc(var(--radius) - 4px)",
55 | },
56 | fontFamily: {
57 | sans: ["var(--font-sans)", ...fontFamily.sans],
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: {height: "0"},
62 | to: {height: "var(--radix-accordion-content-height)"},
63 | },
64 | "accordion-up": {
65 | from: {height: "var(--radix-accordion-content-height)"},
66 | to: {height: "0"},
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
77 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/images/logos/typescript.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/testing.mdx:
--------------------------------------------------------------------------------
1 | # Testing
2 | If you plan to continue building out your app on Supabase, we recommend that you start writing tests for your database. This will help you catch bugs early, and ensure that your app is working as expected.
3 |
4 | To help make testing with Supabase and Basejump easier, we've created a companion library called [supabase-test-helpers](https://github.com/usebasejump/supabase-test-helpers).
5 |
6 | ## Getting started with pgTAP
7 | If you haven't written tests for your Supabase apps yet, we highly recommend you start! It's not too difficult once you get used to the structure - especially now that the Supabase CLI has pgTAP support built in. [Check out the official guide here](https://supabase.com/docs/guides/database/testing), but we've also included a quick start guide below.
8 |
9 | ### 1. Install dbdev extension manager
10 | First, [install the dbdev extension manager into your Supabase instance](https://database.dev/installer).
11 |
12 | ### 2. Installing Supabase Test Helpers
13 | Either add a migration file to your project for the installation, or install it directly into your Supabase instance.
14 |
15 | ```sql
16 | select dbdev.install('basejump-supabase_test_helpers');
17 | ```
18 |
19 | ### 3. Setting up your Supabase project for testing
20 |
21 | ```bash
22 | mkdir -p ./supabase/tests/database
23 | touch ./supabase/tests/database/your-first-tests.test.sql
24 | ```
25 |
26 | ## Writing your first test
27 | Now your'e ready to write your first tests. You can check out a few resources we've pulled together for you to get started:
28 | - [A Guide to testing on Supabase using pgTAP](https://usebasejump.com/blog/testing-on-supabase-with-pgtap) - a blog post we wrote on the topic with commonly used testing formats for your RLS policies
29 | - [Supabase Test Helpers function overview](https://github.com/usebasejump/supabase-test-helpers) - a list of all the functions available to you in the Supabase Test Helpers library
30 | - [Official Supabase testing docs](https://supabase.com/docs/guides/database/testing) - a bit lightweight for now, but I suspect these get fleshed out more over time
31 |
32 | ### Example test
33 | ```sql {{title: "./supabase/tests/database/your-first-tests.test.sql"}}
34 | BEGIN;
35 | -- We activate the extension INSIDE a test so that it's never activated in production
36 | CREATE EXTENSION "basejump-supabase_test_helpers";
37 | -- Indicate how many tests we expect to run
38 | select plan(1);
39 | -- create users for testing
40 | select tests.create_supabase_user('test_owner');
41 | -- create an example post for testing
42 | insert into posts (title, body, user_id) values ('test post', 'this is a test post', tests.get_supabase_uid('test_owner'));
43 | -- ensure you're anon
44 | select tests.clear_authentication();
45 | SELECT
46 | is_empty(
47 | $$ select * from posts $$,
48 | 'Anon cannot select posts'
49 | );
50 | select * from finish();
51 | ROLLBACK;
52 | ```
53 |
54 | ## Running your tests
55 | Now that you've written your first test, you can run it using the Supabase CLI:
56 |
57 | ```bash
58 | supabase test db
59 | ```
60 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import { forwardRef } from 'react'
2 | import Link from 'next/link'
3 | import clsx from 'clsx'
4 | import { motion, useScroll, useTransform } from 'framer-motion'
5 | import { Logo } from '@/components/Logo'
6 | import {
7 | MobileNavigation,
8 | useIsInsideMobileNavigation,
9 | useMobileNavigationStore,
10 | } from '@/components/MobileNavigation'
11 | import { ModeToggle } from '@/components/ModeToggle'
12 | import { MobileSearch, Search } from '@/components/Search'
13 |
14 | function TopLevelNavItem({ href, children }) {
15 | return (
16 |
17 |
21 | {children}
22 |
23 |
24 | )
25 | }
26 |
27 | export const Header = forwardRef(function Header({ className }, ref) {
28 | let { isOpen: mobileNavIsOpen } = useMobileNavigationStore()
29 | let isInsideMobileNavigation = useIsInsideMobileNavigation()
30 |
31 | let { scrollY } = useScroll()
32 | let bgOpacityLight = useTransform(scrollY, [0, 72], [0.5, 0.9])
33 | let bgOpacityDark = useTransform(scrollY, [0, 72], [0.2, 0.8])
34 |
35 | return (
36 |
52 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | )
84 | })
85 |
--------------------------------------------------------------------------------
/apps/basejump-docs/src/pages/docs/react/user-session.mdx:
--------------------------------------------------------------------------------
1 | import {Note} from "@/components/mdx.jsx";
2 | export const meta = {
3 | title: 'SignedIn / SignedOut Components - React',
4 | description: 'Show content when a customer is logged in or logged out '
5 | }
6 |
7 | # SignedIn / SignedOut Component
8 |
9 | These components are used to show content when a user is logged in or out. They're particularly useful for building your navigation.
10 |
11 |
12 | Utility components are only intended to be helpful, they're in no way required. In fact, they don't even touch any Basejump functionality, only Supabase.
13 |
14 |
15 | ## Usage
16 | The `` and `` components need to be nested inside of a `` component. The `` component is responsible for managing the user session and providing it to the other components.
17 |
18 |
19 | ```jsx {{title: 'Next.js'}}
20 | import {BasejumpUserSession,SignedIn,SignedOut} from "@usebasejump/next";
21 | import UserProfileButton from "./UserProfileButton";
22 | import Link from "next/link";
23 |
24 |
25 | export default function Navigation() {
26 | return (
27 |
40 | )
41 | }
42 | ```
43 | ```jsx {{title: 'React'}}
44 | import {BasejumpUserSession,SignedIn} from "@usebasejump/react";
45 | import {createClient} from "@supabase/supabase-js";
46 | import UserProfileButton from "./UserProfileButton";
47 |
48 | export default function Navigation() {
49 | const supabaseClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
50 | return (
51 |
64 | )
65 | }
66 | ```
67 |
68 |
69 | ## Installation
70 |
71 | The ``, `` and `` components are part of the `@usebasejump/react` and `@usebasejump/next` packages. If you've followed the [installation instructions](/docs/react) you should be ready to go.
--------------------------------------------------------------------------------
/apps/basejump-docs/src/components/Heading.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react'
2 | import Link from 'next/link'
3 | import { useInView } from 'framer-motion'
4 |
5 | import { useSectionStore } from '@/components/SectionProvider'
6 | import { Tag } from '@/components/Tag'
7 | import { remToPx } from '@/lib/remToPx'
8 |
9 | function AnchorIcon(props) {
10 | return (
11 |
18 |
19 |
20 | )
21 | }
22 |
23 | function Eyebrow({ tag, label }) {
24 | if (!tag && !label) {
25 | return null
26 | }
27 |
28 | return (
29 |
30 | {tag && {tag}}
31 | {tag && label && (
32 |
33 | )}
34 | {label && (
35 | {label}
36 | )}
37 |
38 | )
39 | }
40 |
41 | function Anchor({ id, inView, children }) {
42 | return (
43 |
47 | {inView && (
48 |
53 | )}
54 | {children}
55 |
56 | )
57 | }
58 |
59 | export function Heading({
60 | level = 2,
61 | children,
62 | id,
63 | tag,
64 | label,
65 | anchor = true,
66 | ...props
67 | }) {
68 | let Component = `h${level}`
69 | let ref = useRef()
70 | let registerHeading = useSectionStore((s) => s.registerHeading)
71 |
72 | let inView = useInView(ref, {
73 | margin: `${remToPx(-3.5)}px 0px 0px 0px`,
74 | amount: 'all',
75 | })
76 |
77 | useEffect(() => {
78 | if (level === 2) {
79 | registerHeading({ id, ref, offsetRem: tag || label ? 8 : 6 })
80 | }
81 | })
82 |
83 | return (
84 | <>
85 |
86 |
92 | {anchor ? (
93 |
94 | {children}
95 |
96 | ) : (
97 | children
98 | )}
99 |
100 | >
101 | )
102 | }
103 |
--------------------------------------------------------------------------------