usebasejump / basejump

Teams, personal accounts, permissions and billing for your Supabase app
https://usebasejump.com
MIT License
708 stars 64 forks source link

RFC: Migrate Basejump to a set of UI Components and API Endpoints #37

Closed tiniscule closed 11 months ago

tiniscule commented 1 year ago

In an effort to make Basejump more useful to people and remove some of the dependency on Next, I'm looking at converting it into a selection of UI components and rpc functions that can be used in any language / framework.

Goals

UI Components

Instead of a full starter template, Basejump would provide a set of UI components that make creating and managing accounts easy. As your startup scales, you could take control over the UI components by creating your own using the API endpoints in the next section.

All components are customized using the same appearance prop as Supabase AuthUI. This allows you to customize the look and feel of the components to match your application.

Account Switcher

import { AccountSwitcher } from '@usebasejump/account-ui-react';

const supabaseClient = useSupabaseClient();

<AccountSwitcher 
    supabaseClient={supabaseClient} 
    appearance={{{theme: ThemeSupa}}} 
    onAccountChange={(account) => router.push(`/accounts/${account.id}`)}
    currentAccount={currentAccount}
/>

Account Settings

Shows an account overview, members and invitations. Let's you create new invitations

import { AccountSettings } from '@usebasejump/account-ui-react';

const supabaseClient = useSupabaseClient();

<AccountSettings 
    supabaseClient={supabaseClient} 
    appearance={{{theme: ThemeSupa}}} 
    currentAccount={currentAccount}
/>

Account Billing

Shows the current billing status and allows you to change the plan

import { AccountBilling } from '@usebasejump/account-ui-react';

const supabaseClient = useSupabaseClient();

<AccountBilling 
    supabaseClient={supabaseClient} 
    appearance={{{theme: ThemeSupa}}} 
    currentAccount={currentAccount}
/>

Account Status Bar

Shows a banner if account status is inactive or delinquent. Allows user to correct the issue.

import { AccountStatusBar } from '@usebasejump/account-ui-react';

const supabaseClient = useSupabaseClient();

<AccountStatusBar 
    supabaseClient={supabaseClient} 
    appearance={{{theme: ThemeSupa}}} 
    currentAccount={currentAccount}
/>

Account Pricing Plans

Shows a list of available pricing plans and allows user to select one

import { AccountPricingPlans } from '@usebasejump/account-ui-react';

const supabaseClient = useSupabaseClient();

<AccountPricingPlans 
    supabaseClient={supabaseClient} 
    appearance={{{theme: ThemeSupa}}} 
    currentAccount={currentAccount} // Optional field
    onPlanClick={(plan) => router.push(`/accounts/${currentAccount.id}/billing`)}
/>

Abstracting away the data model

Proposing that we clean up the public tables and move them into the basejump schema. Only editable tables would remain, such as profiles and a new table called account_settings. This would allow us to hide away the accounts/members/invitations functionality.

RPC / Edge Functions

Loading accounts for the current user

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('get_accounts');
[
  {
    "account_id": "00000000-0000-0000-0000-000000000000",
    "name": "My Account",
    "personal_account": true,
    "created_at": "2021-01-01T00:00:00.000000Z",
    "updated_at": "2021-01-01T00:00:00.000000Z",
    "role": "owner",
    "is_primary_owner": true
  },
  {
    "account_id": "00000000-0000-0000-0000-000000000000",
    "name": "My Team Account",
    "personal_account": false,
    "created_at": "2021-01-01T00:00:00.000000Z",
    "updated_at": "2021-01-01T00:00:00.000000Z",
    "role": "member",
    "is_primary_owner": false
  }
]

Loading a single account

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('get_account', { account_id: '00000000-0000-0000-0000-000000000000' });
{
    "account_id": "00000000-0000-0000-0000-000000000000",
    "name": "My Account",
    "personal_account": true,
    "created_at": "2021-01-01T00:00:00.000000Z",
    "updated_at": "2021-01-01T00:00:00.000000Z",
    "role": "owner",
    "is_primary_owner": true,
    "billing_status": "active",
    "members": [
        {
            "user_id": "00000000-0000-0000-0000-000000000000",
            "email": "test@test.com",
            "role": "owner",
            "is_primary_owner": true,
        }
    ],
    "invitations": [
        {
            "expiration": "2021-01-01T00:00:00.000000Z",
            "email": "test2@test.com",
            "role": "owner"
        }
    ]
}

Creating an account

Create a new account. The current user will be automatically added as the primary account owner

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('create_account', { name: 'Account 2', slug: 'account-2' });
{
    "account_id": "00000000-0000-0000-0000-000000000000",
    "name": "Account 2",
    "personal_account": false,
    "created_at": "2021-01-01T00:00:00.000000Z",
    "updated_at": "2021-01-01T00:00:00.000000Z",
    "role": "owner",
    "billing_status": "active",
    "is_primary_owner": true,
    "members": [
        {
            "user_id": "00000000-0000-0000-0000-000000000000",
            "email": "test@test.com",
            "role": "owner",
            "is_primary_owner": true
        }
    ],
    "invitations": []
}

Invite account member

Invite a new member to an existing account.

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('invite_account_member', { email: 'test3@test.com', role: 'member' });
{
  "expiration": "2021-01-01T00:00:00.000000Z",
  "email": "test3@test.com",
  "role": "member"
}

Accept an account invitation

Allows a user to accept an invitation by providing their invitation token.

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('accept_account_invitation', { token: '00000000-0000-0000-0000-000000000000' });
{
  "account_id": "00000000-0000-0000-0000-000000000000"
}

Update account member role

Allows an account owner to update the role of a member. If the current user is the primary owner, the owner can also transfer ownership to another member.

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .rpc('update_account_role', { user_id: '00000000-0000-0000-0000-000000000000', role: 'owner', make_primary_owner: true });
{
  "user_id": "00000000-0000-0000-0000-000000000000",
  "role": "owner",
  "is_primary_owner": true
}

Account billing status

Returns the current billing status of an account

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .functions.invoke('account_billing_status', { account_id: '00000000-0000-0000-0000-000000000000' });
{
  "subscription_id": "00000000-0000-0000-0000-000000000000",
  "subscription_active": true,
  "status": "active",
  "is_primary_owner": true,
  "billing_email": "test@test.com",
  "account_role": "owner",
  "billing_enabled": true
}

Available billing plans

Returns available billing plans

const supabaseClient = useSupabaseClient();

const { data, error } = await supabaseClient
  .functions.invoke('account_billing_plans', { account_id: '00000000-0000-0000-0000-000000000000' });
[
  {
    "product_name": "Monthly Basic",
    "product_description": "Our most basic plan",
    "currency": "usd",
    "price": 1900,
    "price_id": "price_00000000000000",
    "interval": "monthly",
    "active": true
  },
  {
    "product_name": "Monthly Premium",
    "product_description": "Our most premium plan",
    "currency": "usd",
    "price": 3900,
    "price_id": "price_00000000000001",
    "interval": "monthly",
    "active": false
  }
]
tiniscule commented 1 year ago

This is currently in progress here if anyone is interested in following along https://github.com/usebasejump/basejump/pull/31

jamesthesken commented 1 year ago

Looks great! The headless approach is nice, I've been cobbling together some things from DaisyUI and HeadlessUI/Tailwind. One other thing that looks cool is inviting team members by email.

tiniscule commented 11 months ago

This is now live! Working on the react components over the basejump-js repo.