Adjusted Per seat billing and added example to the sample schema
This commit is contained in:
71
README.md
71
README.md
@@ -661,6 +661,75 @@ NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION=true
|
|||||||
6. **NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS** - to disable team accounts
|
6. **NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS** - to disable team accounts
|
||||||
7. **NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION** - to enable/disable the ability to create a team account
|
7. **NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION** - to enable/disable the ability to create a team account
|
||||||
|
|
||||||
|
#### Personal Accounts vs Team Accounts
|
||||||
|
|
||||||
|
Personal accounts are accounts that are owned by a single user. Team accounts are accounts that are owned by multiple users.
|
||||||
|
|
||||||
|
This allows you to:
|
||||||
|
1. Server B2C customers (personal accounts)
|
||||||
|
2. Serve B2B customers (team accounts)
|
||||||
|
3. Allow both (for example, like GitHub)
|
||||||
|
|
||||||
|
In the vast majority of cases, you will want to enable billing for personal OR team accounts. I have not seen many cases where billing both was required.
|
||||||
|
|
||||||
|
To do so, please set the following variables accordingly.
|
||||||
|
|
||||||
|
For enabling personal accounts billing only:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=true
|
||||||
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=false
|
||||||
|
```
|
||||||
|
|
||||||
|
You may also want to fully disable team accounts:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=false
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable team accounts billing only:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=false
|
||||||
|
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=true
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable both, leave them both as `true`.
|
||||||
|
|
||||||
|
Please remember that for ensuring DB consistency, you need to also set them at DB level by adjusting the table `config`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
create table if not exists public.config(
|
||||||
|
enable_team_accounts boolean default true not null,
|
||||||
|
enable_account_billing boolean default true not null,
|
||||||
|
enable_team_account_billing boolean default true not null,
|
||||||
|
billing_provider public.billing_provider default 'stripe' not null
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable personal account billing:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
alter table public.config
|
||||||
|
set enable_account_billing to true;
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable team account billing:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
alter table public.config
|
||||||
|
set enable_team_account_billing to true;
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable team accounts:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
alter table public.config
|
||||||
|
set enable_team_accounts to false;
|
||||||
|
```
|
||||||
|
|
||||||
|
To leave them both enabled, just leave them as they are.
|
||||||
|
|
||||||
### Cloudflare Turnstile protection
|
### Cloudflare Turnstile protection
|
||||||
|
|
||||||
To use Cloudflare's Turnstile Captcha, you need to set the following environment variables:
|
To use Cloudflare's Turnstile Captcha, you need to set the following environment variables:
|
||||||
@@ -1089,7 +1158,7 @@ export default createBillingSchema({
|
|||||||
id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe',
|
id: 'price_1NNwYHI1i3VnbZTqI2UzaHIe',
|
||||||
name: 'Addon 2',
|
name: 'Addon 2',
|
||||||
cost: 0,
|
cost: 0,
|
||||||
type: 'per-seat',
|
type: 'per_seat',
|
||||||
tiers: [
|
tiers: [
|
||||||
{
|
{
|
||||||
upTo: 3,
|
upTo: 3,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async function PersonalAccountBillingPage() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<div className={'flex flex-col space-y-8'}>
|
<div className={'flex flex-col space-y-4'}>
|
||||||
<If condition={!data}>
|
<If condition={!data}>
|
||||||
<PersonalAccountCheckoutForm customerId={customerId} />
|
<PersonalAccountCheckoutForm customerId={customerId} />
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,11 @@ export class TeamBillingService {
|
|||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
for (const lineItem of lineItems) {
|
for (const lineItem of lineItems) {
|
||||||
if (lineItem.type === 'per-seat') {
|
// check if the line item is a per seat type
|
||||||
|
const isPerSeat = lineItem.type === 'per_seat';
|
||||||
|
|
||||||
|
if (isPerSeat) {
|
||||||
|
// get the current number of members in the account
|
||||||
const quantity = await this.getCurrentMembersCount(accountId);
|
const quantity = await this.getCurrentMembersCount(accountId);
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
@@ -254,6 +258,7 @@ export class TeamBillingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set initial quantity for the line items
|
||||||
return variantQuantities;
|
return variantQuantities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ async function TeamAccountBillingPage({ params }: Params) {
|
|||||||
|
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<div
|
<div
|
||||||
className={cn(`flex w-full flex-col space-y-6`, {
|
className={cn(`flex w-full flex-col space-y-4`, {
|
||||||
'mx-auto max-w-2xl': data,
|
'mx-auto max-w-2xl': data,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -54,18 +54,24 @@ async function ReturnCheckoutSessionPage({ searchParams }: SessionPageProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<BlurryBackdrop />
|
||||||
className={
|
|
||||||
'fixed left-0 top-0 w-full bg-background/30 backdrop-blur-sm' +
|
|
||||||
' !m-0 h-full'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(ReturnCheckoutSessionPage);
|
export default withI18n(ReturnCheckoutSessionPage);
|
||||||
|
|
||||||
|
function BlurryBackdrop() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'fixed left-0 top-0 w-full bg-background/30 backdrop-blur-sm' +
|
||||||
|
' !m-0 h-full'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadCheckoutSession(sessionId: string) {
|
async function loadCheckoutSession(sessionId: string) {
|
||||||
const client = getSupabaseServerComponentClient();
|
const client = getSupabaseServerComponentClient();
|
||||||
const { error } = await requireUser(client);
|
const { error } = await requireUser(client);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default createBillingSchema({
|
|||||||
{
|
{
|
||||||
name: 'Starter Monthly',
|
name: 'Starter Monthly',
|
||||||
id: 'starter-monthly',
|
id: 'starter-monthly',
|
||||||
trialDays: 7,
|
|
||||||
paymentType: 'recurring',
|
paymentType: 'recurring',
|
||||||
interval: 'month',
|
interval: 'month',
|
||||||
lineItems: [
|
lineItems: [
|
||||||
@@ -36,6 +35,26 @@ export default createBillingSchema({
|
|||||||
cost: 9.99,
|
cost: 9.99,
|
||||||
type: 'flat',
|
type: 'flat',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'price_1P8N0zI1i3VnbZTqtUPc1Zvr',
|
||||||
|
name: 'Addon 3',
|
||||||
|
cost: 0,
|
||||||
|
type: 'per_seat',
|
||||||
|
tiers: [
|
||||||
|
{
|
||||||
|
upTo: 1,
|
||||||
|
cost: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
upTo: 5,
|
||||||
|
cost: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
upTo: 'unlimited',
|
||||||
|
cost: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1378,6 +1378,7 @@ on conflict (
|
|||||||
-- Upsert subscription items
|
-- Upsert subscription items
|
||||||
with item_data as (
|
with item_data as (
|
||||||
select
|
select
|
||||||
|
(line_item ->> 'id')::varchar as line_item_id,
|
||||||
(line_item ->> 'product_id')::varchar as prod_id,
|
(line_item ->> 'product_id')::varchar as prod_id,
|
||||||
(line_item ->> 'variant_id')::varchar as var_id,
|
(line_item ->> 'variant_id')::varchar as var_id,
|
||||||
(line_item ->> 'type')::public.subscription_item_type as type,
|
(line_item ->> 'type')::public.subscription_item_type as type,
|
||||||
@@ -1388,6 +1389,7 @@ on conflict (
|
|||||||
from
|
from
|
||||||
jsonb_array_elements(line_items) as line_item)
|
jsonb_array_elements(line_items) as line_item)
|
||||||
insert into public.subscription_items(
|
insert into public.subscription_items(
|
||||||
|
id,
|
||||||
subscription_id,
|
subscription_id,
|
||||||
product_id,
|
product_id,
|
||||||
variant_id,
|
variant_id,
|
||||||
@@ -1397,6 +1399,7 @@ on conflict (
|
|||||||
interval,
|
interval,
|
||||||
interval_count)
|
interval_count)
|
||||||
select
|
select
|
||||||
|
line_item_id,
|
||||||
target_subscription_id,
|
target_subscription_id,
|
||||||
prod_id,
|
prod_id,
|
||||||
var_id,
|
var_id,
|
||||||
@@ -1436,6 +1439,7 @@ grant execute on function public.upsert_subscription(uuid, varchar,
|
|||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
create table if not exists public.subscription_items(
|
create table if not exists public.subscription_items(
|
||||||
|
id varchar(255) not null primary key,
|
||||||
subscription_id text references public.subscriptions(id) on
|
subscription_id text references public.subscriptions(id) on
|
||||||
delete cascade not null,
|
delete cascade not null,
|
||||||
product_id varchar(255) not null,
|
product_id varchar(255) not null,
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
enum LineItemType {
|
||||||
|
Flat = 'flat',
|
||||||
|
PerSeat = 'per_seat',
|
||||||
|
Metered = 'metered',
|
||||||
|
}
|
||||||
|
|
||||||
const BillingIntervalSchema = z.enum(['month', 'year']);
|
const BillingIntervalSchema = z.enum(['month', 'year']);
|
||||||
const LineItemTypeSchema = z.enum(['flat', 'per-seat', 'metered']);
|
const LineItemTypeSchema = z.nativeEnum(LineItemType);
|
||||||
|
|
||||||
export const BillingProviderSchema = z.enum([
|
export const BillingProviderSchema = z.enum([
|
||||||
'stripe',
|
'stripe',
|
||||||
@@ -83,8 +89,10 @@ export const PlanSchema = z
|
|||||||
lineItems: z.array(LineItemSchema).refine(
|
lineItems: z.array(LineItemSchema).refine(
|
||||||
(schema) => {
|
(schema) => {
|
||||||
const types = schema.map((item) => item.type);
|
const types = schema.map((item) => item.type);
|
||||||
const perSeat = types.filter((type) => type === 'per-seat').length;
|
const perSeat = types.filter(
|
||||||
const flat = types.filter((type) => type === 'flat').length;
|
(type) => type === LineItemType.PerSeat,
|
||||||
|
).length;
|
||||||
|
const flat = types.filter((type) => type === LineItemType.Flat).length;
|
||||||
|
|
||||||
return perSeat <= 1 && flat <= 1;
|
return perSeat <= 1 && flat <= 1;
|
||||||
},
|
},
|
||||||
@@ -135,7 +143,7 @@ export const PlanSchema = z
|
|||||||
(data) => {
|
(data) => {
|
||||||
if (data.paymentType === 'one-time') {
|
if (data.paymentType === 'one-time') {
|
||||||
const nonFlatLineItems = data.lineItems.filter(
|
const nonFlatLineItems = data.lineItems.filter(
|
||||||
(item) => item.type !== 'flat',
|
(item) => item.type !== LineItemType.Flat,
|
||||||
);
|
);
|
||||||
|
|
||||||
return nonFlatLineItems.length === 0;
|
return nonFlatLineItems.length === 0;
|
||||||
@@ -314,7 +322,7 @@ export function getPrimaryLineItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const flatLineItem = plan.lineItems.find(
|
const flatLineItem = plan.lineItems.find(
|
||||||
(item) => item.type === 'flat',
|
(item) => item.type === LineItemType.Flat,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (flatLineItem) {
|
if (flatLineItem) {
|
||||||
|
|||||||
@@ -20,10 +20,14 @@ export function EmbeddedCheckout(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CheckoutComponent
|
<>
|
||||||
onClose={props.onClose}
|
<CheckoutComponent
|
||||||
checkoutToken={props.checkoutToken}
|
onClose={props.onClose}
|
||||||
/>
|
checkoutToken={props.checkoutToken}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<BlurryBackdrop />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,3 +102,14 @@ function buildLazyComponent<
|
|||||||
|
|
||||||
return memo(LazyComponent);
|
return memo(LazyComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BlurryBackdrop() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'bg-background/30 fixed left-0 top-0 w-full backdrop-blur-sm' +
|
||||||
|
' !m-0 h-full'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function LineItemDetails(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const FlatFee = () => (
|
const FlatFee = () => (
|
||||||
<div key={item.id} className={'flex flex-col'}>
|
<div className={'flex flex-col'}>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<span className={'flex items-center space-x-1'}>
|
<span className={'flex items-center space-x-1'}>
|
||||||
<span className={'flex items-center space-x-1.5'}>
|
<span className={'flex items-center space-x-1.5'}>
|
||||||
@@ -115,8 +115,8 @@ export function LineItemDetails(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const PerSeat = () => (
|
const PerSeat = () => (
|
||||||
<div className={'flex flex-col'}>
|
<div key={index} className={'flex flex-col'}>
|
||||||
<div key={item.id} className={className}>
|
<div className={className}>
|
||||||
<span className={'flex items-center space-x-1.5'}>
|
<span className={'flex items-center space-x-1.5'}>
|
||||||
<PlusSquare className={'w-4'} />
|
<PlusSquare className={'w-4'} />
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ export function LineItemDetails(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const Metered = () => (
|
const Metered = () => (
|
||||||
<div key={item.id} className={'flex flex-col'}>
|
<div key={index} className={'flex flex-col'}>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<span className={'flex items-center space-x-1'}>
|
<span className={'flex items-center space-x-1'}>
|
||||||
<span className={'flex items-center space-x-1.5'}>
|
<span className={'flex items-center space-x-1.5'}>
|
||||||
@@ -179,13 +179,13 @@ export function LineItemDetails(
|
|||||||
|
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
case 'flat':
|
case 'flat':
|
||||||
return <FlatFee />;
|
return <FlatFee key={item.id} />;
|
||||||
|
|
||||||
case 'per-seat':
|
case 'per_seat':
|
||||||
return <PerSeat />;
|
return <PerSeat key={item.id} />;
|
||||||
|
|
||||||
case 'metered': {
|
case 'metered': {
|
||||||
return <Metered />;
|
return <Metered key={item.id} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ function PricingItem(
|
|||||||
`animate-in slide-in-from-left-4 fade-in text-sm capitalize`,
|
`animate-in slide-in-from-left-4 fade-in text-sm capitalize`,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<If condition={props.primaryLineItem.type === 'per-seat'}>
|
<If condition={props.primaryLineItem.type === 'per_seat'}>
|
||||||
<Trans i18nKey={'billing:perTeamMember'} />
|
<Trans i18nKey={'billing:perTeamMember'} />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export class BillingEventHandlerService {
|
|||||||
|
|
||||||
// Handle the subscription deleted event
|
// Handle the subscription deleted event
|
||||||
// here we delete the subscription from the database
|
// here we delete the subscription from the database
|
||||||
logger.info(ctx, 'Processing subscription deleted event');
|
logger.info(ctx, 'Processing subscription deleted event...');
|
||||||
|
|
||||||
const { error } = await client
|
const { error } = await client
|
||||||
.from('subscriptions')
|
.from('subscriptions')
|
||||||
|
|||||||
@@ -127,16 +127,4 @@ export class BillingGatewayService {
|
|||||||
|
|
||||||
return strategy.updateSubscription(payload);
|
return strategy.updateSubscription(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a plan by the specified plan ID.
|
|
||||||
* @param planId
|
|
||||||
*/
|
|
||||||
async getPlanById(planId: string) {
|
|
||||||
const strategy = await BillingGatewayFactoryService.GetProviderStrategy(
|
|
||||||
this.provider,
|
|
||||||
);
|
|
||||||
|
|
||||||
return strategy.getPlanById(planId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,12 +261,12 @@ export class StripeWebhookHandlerService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private handleSubscriptionDeletedEvent(
|
private handleSubscriptionDeletedEvent(
|
||||||
subscription: Stripe.CustomerSubscriptionDeletedEvent,
|
event: Stripe.CustomerSubscriptionDeletedEvent,
|
||||||
onSubscriptionDeletedCallback: (subscriptionId: string) => Promise<unknown>,
|
onSubscriptionDeletedCallback: (subscriptionId: string) => Promise<unknown>,
|
||||||
) {
|
) {
|
||||||
// Here we don't need to do anything, so we just return the callback
|
// Here we don't need to do anything, so we just return the callback
|
||||||
|
|
||||||
return onSubscriptionDeletedCallback(subscription.id);
|
return onSubscriptionDeletedCallback(event.data.object.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildSubscriptionPayload<
|
private buildSubscriptionPayload<
|
||||||
@@ -298,6 +298,7 @@ export class StripeWebhookHandlerService
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
quantity,
|
quantity,
|
||||||
subscription_id: params.id,
|
subscription_id: params.id,
|
||||||
|
subscription_item_id: item.id,
|
||||||
product_id: item.price?.product as string,
|
product_id: item.price?.product as string,
|
||||||
variant_id: variantId,
|
variant_id: variantId,
|
||||||
price_amount: item.price?.unit_amount,
|
price_amount: item.price?.unit_amount,
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ function useGetColumns(
|
|||||||
permissions={permissions}
|
permissions={permissions}
|
||||||
member={row.original}
|
member={row.original}
|
||||||
currentUserId={params.currentUserId}
|
currentUserId={params.currentUserId}
|
||||||
accountId={params.currentAccountId}
|
currentTeamAccountId={params.currentAccountId}
|
||||||
currentRoleHierarchy={params.currentRoleHierarchy}
|
currentRoleHierarchy={params.currentRoleHierarchy}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
@@ -206,12 +206,13 @@ function ActionsDropdown({
|
|||||||
permissions,
|
permissions,
|
||||||
member,
|
member,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
|
currentTeamAccountId,
|
||||||
currentRoleHierarchy,
|
currentRoleHierarchy,
|
||||||
}: {
|
}: {
|
||||||
permissions: Permissions;
|
permissions: Permissions;
|
||||||
member: Members[0];
|
member: Members[0];
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
accountId: string;
|
currentTeamAccountId: string;
|
||||||
currentRoleHierarchy: number;
|
currentRoleHierarchy: number;
|
||||||
}) {
|
}) {
|
||||||
const [isRemoving, setIsRemoving] = useState(false);
|
const [isRemoving, setIsRemoving] = useState(false);
|
||||||
@@ -275,7 +276,7 @@ function ActionsDropdown({
|
|||||||
<RemoveMemberDialog
|
<RemoveMemberDialog
|
||||||
isOpen
|
isOpen
|
||||||
setIsOpen={setIsRemoving}
|
setIsOpen={setIsRemoving}
|
||||||
accountId={member.id}
|
teamAccountId={currentTeamAccountId}
|
||||||
userId={member.user_id}
|
userId={member.user_id}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
@@ -286,7 +287,7 @@ function ActionsDropdown({
|
|||||||
setIsOpen={setIsUpdatingRole}
|
setIsOpen={setIsUpdatingRole}
|
||||||
userId={member.user_id}
|
userId={member.user_id}
|
||||||
userRole={member.role}
|
userRole={member.role}
|
||||||
teamAccountId={member.account_id}
|
teamAccountId={currentTeamAccountId}
|
||||||
userRoleHierarchy={currentRoleHierarchy}
|
userRoleHierarchy={currentRoleHierarchy}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import { removeMemberFromAccountAction } from '../../server/actions/team-members
|
|||||||
export const RemoveMemberDialog: React.FC<{
|
export const RemoveMemberDialog: React.FC<{
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: (isOpen: boolean) => void;
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
accountId: string;
|
teamAccountId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
}> = ({ isOpen, setIsOpen, accountId, userId }) => {
|
}> = ({ isOpen, setIsOpen, teamAccountId, userId }) => {
|
||||||
return (
|
return (
|
||||||
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
@@ -37,7 +37,7 @@ export const RemoveMemberDialog: React.FC<{
|
|||||||
|
|
||||||
<RemoveMemberForm
|
<RemoveMemberForm
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
accountId={accountId}
|
accountId={teamAccountId}
|
||||||
userId={userId}
|
userId={userId}
|
||||||
/>
|
/>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class AccountPerSeatBillingService {
|
|||||||
id,
|
id,
|
||||||
subscription_items !inner (
|
subscription_items !inner (
|
||||||
quantity,
|
quantity,
|
||||||
id: variant_id,
|
id,
|
||||||
type
|
type
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user