Returns: boolean
@@ -1748,6 +1770,7 @@ export type Database = {
invitation: {
email: string | null
role: string | null
+ personal_code: string | null
}
}
}
diff --git a/packages/supabase/src/hooks/use-sign-up-with-email-password.ts b/packages/supabase/src/hooks/use-sign-up-with-email-password.ts
index 821482f..a387d0e 100644
--- a/packages/supabase/src/hooks/use-sign-up-with-email-password.ts
+++ b/packages/supabase/src/hooks/use-sign-up-with-email-password.ts
@@ -3,6 +3,7 @@ import { useMutation } from '@tanstack/react-query';
import { useSupabase } from './use-supabase';
interface Credentials {
+ personalCode: string;
email: string;
password: string;
emailRedirectTo: string;
@@ -14,13 +15,17 @@ export function useSignUpWithEmailAndPassword() {
const mutationKey = ['auth', 'sign-up-with-email-password'];
const mutationFn = async (params: Credentials) => {
- const { emailRedirectTo, captchaToken, ...credentials } = params;
-
+ const { emailRedirectTo, captchaToken, personalCode, ...credentials } = params;
+
+ // TODO?: should be a validation of unique personal code before registration
const response = await client.auth.signUp({
...credentials,
options: {
emailRedirectTo,
captchaToken,
+ data: {
+ personalCode
+ }
},
});
diff --git a/packages/ui/src/makerkit/page.tsx b/packages/ui/src/makerkit/page.tsx
index 3d823b1..f9cf333 100644
--- a/packages/ui/src/makerkit/page.tsx
+++ b/packages/ui/src/makerkit/page.tsx
@@ -83,7 +83,7 @@ function PageWithHeader(props: PageProps) {
>
> 'name' is not null then
+ user_name := new.raw_user_meta_data ->> 'name';
+ end if;
+
+ if user_name is null and new.email is not null then
+ user_name := split_part(new.email, '@', 1);
+ end if;
+
+ if user_name is null then
+ user_name := '';
+ end if;
+
+ if new.raw_user_meta_data ->> 'avatar_url' is not null then
+ picture_url := new.raw_user_meta_data ->> 'avatar_url';
+ else
+ picture_url := null;
+ end if;
+
+ personal_code := new.raw_user_meta_data ->> 'personalCode';
+
+ insert into public.accounts (
+ id,
+ primary_owner_user_id,
+ name,
+ is_personal_account,
+ picture_url,
+ email,
+ personal_code
+ )
+ values (
+ new.id,
+ new.id,
+ user_name,
+ true,
+ picture_url,
+ new.email,
+ personal_code
+ );
+
+ return new;
+end;
+$$;
diff --git a/supabase/migrations/20250630115016_add_rpc_get_invitations_with_account_ids.sql b/supabase/migrations/20250630115016_add_rpc_get_invitations_with_account_ids.sql
new file mode 100644
index 0000000..a972122
--- /dev/null
+++ b/supabase/migrations/20250630115016_add_rpc_get_invitations_with_account_ids.sql
@@ -0,0 +1,20 @@
+create or replace function public.get_invitations_with_account_ids(
+ company_id uuid,
+ personal_codes text[]
+)
+returns table (
+ invite_token text,
+ personal_code text,
+ account_id uuid
+)
+language sql
+as $$
+ select
+ i.invite_token,
+ i.personal_code,
+ a.id as account_id
+ from public.invitations i
+ join public.accounts a on a.personal_code = i.personal_code
+ where i.account_id = company_id
+ and i.personal_code = any(personal_codes);
+$$;
diff --git a/supabase/migrations/20250630163951_alter_accounts_personal_code.sql b/supabase/migrations/20250630163951_alter_accounts_personal_code.sql
new file mode 100644
index 0000000..2a6fde1
--- /dev/null
+++ b/supabase/migrations/20250630163951_alter_accounts_personal_code.sql
@@ -0,0 +1,27 @@
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1
+ FROM pg_constraint
+ WHERE conname = 'accounts_personal_code_unique'
+ ) THEN
+ ALTER TABLE public.accounts
+ ADD CONSTRAINT accounts_personal_code_unique UNIQUE (personal_code);
+ END IF;
+END$$;
+
+ALTER TABLE public.invitations
+ALTER COLUMN personal_code TYPE text;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1
+ FROM pg_constraint
+ WHERE conname = 'invitations_personal_code_unique'
+ ) THEN
+ ALTER TABLE public.invitations
+ ADD CONSTRAINT invitations_personal_code_unique UNIQUE (personal_code);
+ END IF;
+END$$;
+
diff --git a/supabase/migrations/20250630170525_add_personal_code_to_invitations.sql b/supabase/migrations/20250630170525_add_personal_code_to_invitations.sql
new file mode 100644
index 0000000..2170efb
--- /dev/null
+++ b/supabase/migrations/20250630170525_add_personal_code_to_invitations.sql
@@ -0,0 +1,146 @@
+drop function if exists public.add_invitations_to_account(text, public.invitation[]);
+
+drop type if exists public.invitation;
+
+create type public.invitation as (
+ email text,
+ role text,
+ personal_code char(11)
+);
+
+create or replace function public.add_invitations_to_account (
+ account_slug text,
+ invitations public.invitation[]
+) returns public.invitations[]
+ set search_path = ''
+as $$
+declare
+ new_invitation public.invitations;
+ all_invitations public.invitations[] := array[]::public.invitations[];
+ invite_token text;
+ invite public.invitation;
+begin
+ foreach invite in array invitations loop
+ invite_token := extensions.uuid_generate_v4();
+
+ insert into public.invitations (
+ email,
+ account_id,
+ invited_by,
+ role,
+ invite_token,
+ personal_code
+ )
+ values (
+ invite.email,
+ (select id from public.accounts where slug = account_slug),
+ auth.uid(),
+ invite.role,
+ invite_token,
+ invite.personal_code
+ )
+ returning * into new_invitation;
+
+ all_invitations := array_append(all_invitations, new_invitation);
+ end loop;
+
+ return all_invitations;
+end;
+$$ language plpgsql;
+
+grant execute on function public.add_invitations_to_account(text, public.invitation[]) to authenticated;
+
+drop function if exists public.get_account_invitations(text);
+
+create function public.get_account_invitations (account_slug text)
+returns table (
+ id integer,
+ email varchar(255),
+ account_id uuid,
+ invited_by uuid,
+ role varchar(50),
+ created_at timestamptz,
+ updated_at timestamptz,
+ expires_at timestamptz,
+ personal_code text,
+ inviter_name varchar,
+ inviter_email varchar
+)
+set search_path = ''
+as $$
+begin
+ return query
+ select
+ invitation.id,
+ invitation.email,
+ invitation.account_id,
+ invitation.invited_by,
+ invitation.role,
+ invitation.created_at,
+ invitation.updated_at,
+ invitation.expires_at,
+ invitation.personal_code,
+ account.name,
+ account.email
+ from
+ public.invitations as invitation
+ join public.accounts as account on invitation.account_id = account.id
+ where
+ account.slug = account_slug;
+end;
+$$ language plpgsql;
+
+grant execute on function public.get_account_invitations(text) to authenticated, service_role;
+
+
+drop function if exists public.get_account_members(text);
+
+-- Functions "public.get_account_members"
+-- Function to get the members of an account by the account slug
+create
+ or replace function public.get_account_members (account_slug text) returns table (
+ id uuid,
+ user_id uuid,
+ account_id uuid,
+ role varchar(50),
+ role_hierarchy_level int,
+ primary_owner_user_id uuid,
+ name varchar,
+ email varchar,
+ personal_code text,
+ picture_url varchar,
+ created_at timestamptz,
+ updated_at timestamptz
+ ) language plpgsql
+ set
+ search_path = '' as $$
+begin
+ return QUERY
+ select
+ acc.id,
+ am.user_id,
+ am.account_id,
+ am.account_role,
+ r.hierarchy_level,
+ a.primary_owner_user_id,
+ acc.name,
+ acc.email,
+ acc.personal_code,
+ acc.picture_url,
+ am.created_at,
+ am.updated_at
+ from
+ public.accounts_memberships am
+ join public.accounts a on a.id = am.account_id
+ join public.accounts acc on acc.id = am.user_id
+ join public.roles r on r.name = am.account_role
+ where
+ a.slug = account_slug;
+
+end;
+
+$$;
+
+grant
+ execute on function public.get_account_members (text) to authenticated,
+ service_role;
diff --git a/supabase/migrations/20250630191934_is_company_admin.sql b/supabase/migrations/20250630191934_is_company_admin.sql
new file mode 100644
index 0000000..3ccbeb0
--- /dev/null
+++ b/supabase/migrations/20250630191934_is_company_admin.sql
@@ -0,0 +1,22 @@
+create or replace function public.is_company_admin(account_slug text)
+returns boolean
+set search_path = ''
+language plpgsql
+as $$
+declare
+ is_owner boolean;
+begin
+ select exists (
+ select 1
+ from public.accounts_memberships am
+ join public.accounts a on a.id = am.account_id
+ where am.user_id = auth.uid()
+ and am.account_role = 'owner'
+ and a.slug = account_slug
+ ) into is_owner;
+
+ return is_owner;
+end;
+$$;
+
+grant execute on function public.is_company_admin(text) to authenticated, service_role;