├── .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 | 6 | 7 | 8 | 9 | 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 | Basejump Logo 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 | 2 | 4 | 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 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 |
    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 |
    27 | 30 |
    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 |
    26 |

    🐯

    27 |

    28 | There's treasure everywhere 29 |

    30 |
    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 | 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 | Basejump Logo 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 | 6 | ) 7 | } 8 | 9 | export function GitHubIcon(props) { 10 | return ( 11 | 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 | 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 | 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 |
    49 |
    50 | 51 |
    52 |
    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 | --------------------------------------------------------------------------------