Rename "Organization" to "Team" across web app and update related services
Renamed all instances of "Organization" with "Team" across the entire web application to reflect the latest change in terminology. This further extends to renaming related services, components, and their respective invocation instances. Separate billing permissions have been defined for Team accounts, and security actions have been updated in SQL schema along with some layout adjustments.
This commit is contained in:
@@ -2,8 +2,8 @@ import { notFound } from 'next/navigation';
|
|||||||
|
|
||||||
import featureFlagsConfig from '~/config/feature-flags.config';
|
import featureFlagsConfig from '~/config/feature-flags.config';
|
||||||
|
|
||||||
function OrganizationAccountBillingLayout(props: React.PropsWithChildren) {
|
function TeamAccountBillingLayout(props: React.PropsWithChildren) {
|
||||||
const isEnabled = featureFlagsConfig.enableOrganizationBilling;
|
const isEnabled = featureFlagsConfig.enableTeamAccountBilling;
|
||||||
|
|
||||||
if (!isEnabled) {
|
if (!isEnabled) {
|
||||||
notFound();
|
notFound();
|
||||||
@@ -12,4 +12,4 @@ function OrganizationAccountBillingLayout(props: React.PropsWithChildren) {
|
|||||||
return <>{props.children}</>;
|
return <>{props.children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OrganizationAccountBillingLayout;
|
export default TeamAccountBillingLayout;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ interface Params {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OrganizationAccountBillingPage({ params }: Params) {
|
async function TeamAccountBillingPage({ params }: Params) {
|
||||||
const workspace = await loadTeamWorkspace(params.account);
|
const workspace = await loadTeamWorkspace(params.account);
|
||||||
const accountId = workspace.account.id;
|
const accountId = workspace.account.id;
|
||||||
const [subscription, customerId] = await loadAccountData(accountId);
|
const [subscription, customerId] = await loadAccountData(accountId);
|
||||||
@@ -60,7 +60,7 @@ async function OrganizationAccountBillingPage({ params }: Params) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(OrganizationAccountBillingPage);
|
export default withI18n(TeamAccountBillingPage);
|
||||||
|
|
||||||
async function loadAccountData(accountId: string) {
|
async function loadAccountData(accountId: string) {
|
||||||
const client = getSupabaseServerComponentClient();
|
const client = getSupabaseServerComponentClient();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ interface Params {
|
|||||||
account: string;
|
account: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OrganizationWorkspaceLayout({
|
function TeamWorkspaceLayout({
|
||||||
children,
|
children,
|
||||||
params,
|
params,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
@@ -48,7 +48,7 @@ function OrganizationWorkspaceLayout({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(OrganizationWorkspaceLayout);
|
export default withI18n(TeamWorkspaceLayout);
|
||||||
|
|
||||||
function getUIStateCookies() {
|
function getUIStateCookies() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ async function loadInvitations(account: string) {
|
|||||||
return data ?? [];
|
return data ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function OrganizationAccountMembersPage({ params }: Params) {
|
async function TeamAccountMembersPage({ params }: Params) {
|
||||||
const slug = params.account;
|
const slug = params.account;
|
||||||
|
|
||||||
const [{ account, user }, members, invitations] = await Promise.all([
|
const [{ account, user }, members, invitations] = await Promise.all([
|
||||||
@@ -142,4 +142,4 @@ async function OrganizationAccountMembersPage({ params }: Params) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(OrganizationAccountMembersPage);
|
export default withI18n(TeamAccountMembersPage);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const metadata = {
|
|||||||
title: 'Organization Account Home',
|
title: 'Organization Account Home',
|
||||||
};
|
};
|
||||||
|
|
||||||
function OrganizationAccountHomePage({
|
function TeamAccountHomePage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: {
|
params: {
|
||||||
@@ -62,4 +62,4 @@ function OrganizationAccountHomePage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n(OrganizationAccountHomePage);
|
export default withI18n(TeamAccountHomePage);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const routes = (account: string) => [
|
|||||||
path: createPath(pathsConfig.app.accountMembers, account),
|
path: createPath(pathsConfig.app.accountMembers, account),
|
||||||
Icon: <Users className={iconClasses} />,
|
Icon: <Users className={iconClasses} />,
|
||||||
},
|
},
|
||||||
featureFlagsConfig.enableOrganizationBilling
|
featureFlagsConfig.enableTeamAccountBilling
|
||||||
? {
|
? {
|
||||||
label: 'common:billingTabLabel',
|
label: 'common:billingTabLabel',
|
||||||
path: createPath(pathsConfig.app.accountBilling, account),
|
path: createPath(pathsConfig.app.accountBilling, account),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"homeTabLabel": "Home",
|
"homeTabLabel": "Home",
|
||||||
"homeTabDescription": "Welcome to your home page",
|
"homeTabDescription": "Welcome to your home page",
|
||||||
"accountMembers": "Members",
|
"accountMembers": "Members",
|
||||||
|
"membersTabDescription": "Manage your organization's members",
|
||||||
"billingTabLabel": "Billing",
|
"billingTabLabel": "Billing",
|
||||||
"billingTabDescription": "Manage your billing and subscription",
|
"billingTabDescription": "Manage your billing and subscription",
|
||||||
"yourAccountTabLabel": "Account Settings",
|
"yourAccountTabLabel": "Account Settings",
|
||||||
|
|||||||
@@ -35,25 +35,30 @@ export class DeletePersonalAccountService {
|
|||||||
productName: string;
|
productName: string;
|
||||||
};
|
};
|
||||||
}) {
|
}) {
|
||||||
|
const userId = params.userId;
|
||||||
|
|
||||||
Logger.info(
|
Logger.info(
|
||||||
{ userId: params.userId, name: this.namespace },
|
{ name: this.namespace, userId },
|
||||||
'User requested deletion. Processing...',
|
'User requested deletion. Processing...',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cancel all user subscriptions
|
// Cancel all user subscriptions
|
||||||
const billingService = new AccountBillingService(params.adminClient);
|
const billingService = new AccountBillingService(params.adminClient);
|
||||||
|
|
||||||
await billingService.cancelAllAccountSubscriptions(params.userId);
|
await billingService.cancelAllAccountSubscriptions({
|
||||||
|
userId,
|
||||||
|
accountId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
// execute the deletion of the user
|
// execute the deletion of the user
|
||||||
try {
|
try {
|
||||||
await params.adminClient.auth.admin.deleteUser(params.userId);
|
await params.adminClient.auth.admin.deleteUser(userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(
|
Logger.error(
|
||||||
{
|
{
|
||||||
userId: params.userId,
|
|
||||||
error,
|
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
|
userId,
|
||||||
|
error,
|
||||||
},
|
},
|
||||||
'Error deleting user',
|
'Error deleting user',
|
||||||
);
|
);
|
||||||
@@ -66,8 +71,8 @@ export class DeletePersonalAccountService {
|
|||||||
try {
|
try {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
{
|
{
|
||||||
userId: params.userId,
|
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
|
userId,
|
||||||
},
|
},
|
||||||
`Sending account deletion email...`,
|
`Sending account deletion email...`,
|
||||||
);
|
);
|
||||||
@@ -81,8 +86,8 @@ export class DeletePersonalAccountService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(
|
Logger.error(
|
||||||
{
|
{
|
||||||
userId: params.userId,
|
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
|
userId,
|
||||||
error,
|
error,
|
||||||
},
|
},
|
||||||
`Error sending account deletion email`,
|
`Error sending account deletion email`,
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ export async function deleteTeamAccountAction(formData: FormData) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
|
const auth = await requireAuth(client);
|
||||||
|
|
||||||
|
if (auth.error) {
|
||||||
|
throw new Error('Authentication required');
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the user has the necessary permissions to delete the team account
|
// Check if the user has the necessary permissions to delete the team account
|
||||||
await assertUserPermissionsToDeleteTeamAccount(client, params.accountId);
|
await assertUserPermissionsToDeleteTeamAccount(client, params.accountId);
|
||||||
@@ -29,7 +34,10 @@ export async function deleteTeamAccountAction(formData: FormData) {
|
|||||||
getSupabaseServerActionClient({
|
getSupabaseServerActionClient({
|
||||||
admin: true,
|
admin: true,
|
||||||
}),
|
}),
|
||||||
params,
|
{
|
||||||
|
accountId: params.accountId,
|
||||||
|
userId: auth.data.user.id,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return redirect('/home');
|
return redirect('/home');
|
||||||
|
|||||||
@@ -20,12 +20,16 @@ export class DeleteTeamAccountService {
|
|||||||
*/
|
*/
|
||||||
async deleteTeamAccount(
|
async deleteTeamAccount(
|
||||||
adminClient: SupabaseClient<Database>,
|
adminClient: SupabaseClient<Database>,
|
||||||
params: { accountId: string },
|
params: {
|
||||||
|
accountId: string;
|
||||||
|
userId: string;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
{
|
{
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
userId: params.userId,
|
||||||
},
|
},
|
||||||
`Requested team account deletion. Processing...`,
|
`Requested team account deletion. Processing...`,
|
||||||
);
|
);
|
||||||
@@ -34,6 +38,7 @@ export class DeleteTeamAccountService {
|
|||||||
{
|
{
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
userId: params.userId,
|
||||||
},
|
},
|
||||||
`Deleting all account subscriptions...`,
|
`Deleting all account subscriptions...`,
|
||||||
);
|
);
|
||||||
@@ -41,7 +46,7 @@ export class DeleteTeamAccountService {
|
|||||||
// First - we want to cancel all Stripe active subscriptions
|
// First - we want to cancel all Stripe active subscriptions
|
||||||
const billingService = new AccountBillingService(adminClient);
|
const billingService = new AccountBillingService(adminClient);
|
||||||
|
|
||||||
await billingService.cancelAllAccountSubscriptions(params.accountId);
|
await billingService.cancelAllAccountSubscriptions(params);
|
||||||
|
|
||||||
// now we can use the admin client to delete the account.
|
// now we can use the admin client to delete the account.
|
||||||
const { error } = await adminClient
|
const { error } = await adminClient
|
||||||
@@ -54,6 +59,7 @@ export class DeleteTeamAccountService {
|
|||||||
{
|
{
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
userId: params.userId,
|
||||||
error,
|
error,
|
||||||
},
|
},
|
||||||
'Failed to delete team account',
|
'Failed to delete team account',
|
||||||
@@ -66,6 +72,7 @@ export class DeleteTeamAccountService {
|
|||||||
{
|
{
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
userId: params.userId,
|
||||||
},
|
},
|
||||||
'Successfully deleted team account',
|
'Successfully deleted team account',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ const badgeVariants = cva(
|
|||||||
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
|
||||||
outline: 'text-foreground',
|
outline: 'text-foreground',
|
||||||
success:
|
success:
|
||||||
'border-transparent bg-green-50 text-green-500 dark:bg-green-500/20',
|
'border-transparent bg-green-50 hover:bg-green-50 text-green-500 dark:bg-green-500/20 dark:hover:bg-green-500/20',
|
||||||
warning:
|
warning:
|
||||||
'border-transparent bg-orange-50 text-orange-500 dark:bg-transparent',
|
'border-transparent bg-orange-50 hover:bg-orange-50 text-orange-500 dark:bg-orange-500/20 dark:hover:bg-orange-500/20',
|
||||||
info: 'border-transparent bg-blue-50 text-blue-500 dark:bg-transparent',
|
info: 'border-transparent bg-blue-50 hover:bg-blue-50 text-blue-500 dark:bg-blue-500/20 dark:hover:bg-blue-500/20',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ begin
|
|||||||
|
|
||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
|
|
||||||
grant execute on function kit.can_remove_account_member (uuid, uuid) to authenticated, postgres;
|
grant execute on function kit.can_remove_account_member (uuid, uuid) to authenticated, service_role;
|
||||||
|
|
||||||
-- RLS
|
-- RLS
|
||||||
-- SELECT: Users can read their team members account memberships
|
-- SELECT: Users can read their team members account memberships
|
||||||
@@ -649,7 +649,7 @@ begin
|
|||||||
|
|
||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
|
|
||||||
grant execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, postgres;
|
grant execute on function public.has_permission (uuid, uuid, public.app_permissions) to authenticated, service_role;
|
||||||
|
|
||||||
-- Enable RLS on the role_permissions table
|
-- Enable RLS on the role_permissions table
|
||||||
alter table public.role_permissions enable row level security;
|
alter table public.role_permissions enable row level security;
|
||||||
@@ -1116,7 +1116,7 @@ end;
|
|||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
|
|
||||||
grant
|
grant
|
||||||
execute on function public.create_account (text) to authenticated;
|
execute on function public.create_account (text) to authenticated, service_role;
|
||||||
|
|
||||||
-- RLS
|
-- RLS
|
||||||
-- Authenticated users can create organization accounts
|
-- Authenticated users can create organization accounts
|
||||||
@@ -1194,7 +1194,7 @@ where
|
|||||||
grant
|
grant
|
||||||
select
|
select
|
||||||
on public.user_account_workspace to authenticated,
|
on public.user_account_workspace to authenticated,
|
||||||
postgres;
|
service_role;
|
||||||
|
|
||||||
create or replace view
|
create or replace view
|
||||||
public.user_accounts as
|
public.user_accounts as
|
||||||
@@ -1214,7 +1214,7 @@ where
|
|||||||
grant
|
grant
|
||||||
select
|
select
|
||||||
on public.user_accounts to authenticated,
|
on public.user_accounts to authenticated,
|
||||||
postgres;
|
service_role;
|
||||||
|
|
||||||
create
|
create
|
||||||
or replace function public.organization_account_workspace (account_slug text) returns table (
|
or replace function public.organization_account_workspace (account_slug text) returns table (
|
||||||
@@ -1256,7 +1256,7 @@ $$ language plpgsql;
|
|||||||
|
|
||||||
grant
|
grant
|
||||||
execute on function public.organization_account_workspace (text) to authenticated,
|
execute on function public.organization_account_workspace (text) to authenticated,
|
||||||
postgres;
|
service_role;
|
||||||
|
|
||||||
CREATE
|
CREATE
|
||||||
OR REPLACE FUNCTION public.get_account_members (account_slug text) RETURNS TABLE (
|
OR REPLACE FUNCTION public.get_account_members (account_slug text) RETURNS TABLE (
|
||||||
@@ -1283,7 +1283,7 @@ $$;
|
|||||||
|
|
||||||
grant
|
grant
|
||||||
execute on function public.get_account_members (text) to authenticated,
|
execute on function public.get_account_members (text) to authenticated,
|
||||||
postgres;
|
service_role;
|
||||||
|
|
||||||
create or replace function public.get_account_invitations(account_slug text) returns table (
|
create or replace function public.get_account_invitations(account_slug text) returns table (
|
||||||
id integer,
|
id integer,
|
||||||
@@ -1316,7 +1316,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
$$ language plpgsql;
|
$$ language plpgsql;
|
||||||
|
|
||||||
grant execute on function public.get_account_invitations (text) to authenticated, postgres;
|
grant execute on function public.get_account_invitations (text) to authenticated, service_role;
|
||||||
|
|
||||||
CREATE TYPE kit.invitation AS (
|
CREATE TYPE kit.invitation AS (
|
||||||
email text,
|
email text,
|
||||||
@@ -1359,7 +1359,7 @@ BEGIN
|
|||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
grant execute on function public.add_invitations_to_account (text, kit.invitation[]) to authenticated, postgres;
|
grant execute on function public.add_invitations_to_account (text, kit.invitation[]) to authenticated, service_role;
|
||||||
|
|
||||||
-- Storage
|
-- Storage
|
||||||
-- Account Image
|
-- Account Image
|
||||||
@@ -1368,25 +1368,27 @@ insert into
|
|||||||
values
|
values
|
||||||
('account_image', 'account_image', true);
|
('account_image', 'account_image', true);
|
||||||
|
|
||||||
|
create or replace function kit.get_storage_filename_as_uuid (name text) returns uuid as $$
|
||||||
|
begin
|
||||||
|
return replace(
|
||||||
|
storage.filename (name),
|
||||||
|
concat('.', storage.extension (name)),
|
||||||
|
''
|
||||||
|
)::uuid;
|
||||||
|
end;
|
||||||
|
$$ language plpgsql;
|
||||||
|
|
||||||
|
grant execute on function kit.get_storage_filename_as_uuid (text) to authenticated, service_role;
|
||||||
|
|
||||||
-- RLS policies for storage
|
-- RLS policies for storage
|
||||||
create policy account_image on storage.objects for all using (
|
create policy account_image on storage.objects for all using (
|
||||||
bucket_id = 'account_image'
|
bucket_id = 'account_image'
|
||||||
and (
|
and kit.get_storage_filename_as_uuid(name) = auth.uid () or
|
||||||
replace(
|
public.has_role_on_account(kit.get_storage_filename_as_uuid(name))
|
||||||
storage.filename (name),
|
|
||||||
concat('.', storage.extension (name)),
|
|
||||||
''
|
|
||||||
)::uuid
|
|
||||||
) = auth.uid ()
|
|
||||||
)
|
)
|
||||||
with
|
with
|
||||||
check (
|
check (
|
||||||
bucket_id = 'account_image'
|
bucket_id = 'account_image'
|
||||||
and (
|
and kit.get_storage_filename_as_uuid(name) = auth.uid () or
|
||||||
replace(
|
public.has_permission(auth.uid(), kit.get_storage_filename_as_uuid(name), 'settings.manage')
|
||||||
storage.filename (name),
|
|
||||||
concat('.', storage.extension (name)),
|
|
||||||
''
|
|
||||||
)::uuid
|
|
||||||
) = auth.uid ()
|
|
||||||
);
|
);
|
||||||
Reference in New Issue
Block a user