feat: implement membership confirmation flow and update related functionalities

This commit is contained in:
Danel Kungla
2025-07-08 15:59:08 +03:00
parent 848dbb1618
commit 10580fa653
18 changed files with 188 additions and 179 deletions

View File

@@ -47,16 +47,13 @@ class AccountsApi {
}
/**
* @name loadUserAccounts
* Load only user-owned accounts (not just memberships).
*/
* @name loadUserAccounts
* Load only user-owned accounts (not just memberships).
*/
async loadUserAccounts() {
const authUser = await this.client.auth.getUser();
const {
data,
error: userError,
} = authUser
const { data, error: userError } = authUser;
if (userError) {
throw userError;
@@ -66,14 +63,16 @@ class AccountsApi {
const { data: accounts, error } = await this.client
.from('accounts_memberships')
.select(`
.select(
`
account_id,
user_accounts (
name,
slug,
picture_url
picture_url,
)
`,
)
`)
.eq('user_id', user.id)
.eq('account_role', 'owner');
@@ -88,7 +87,6 @@ class AccountsApi {
}));
}
async loadTempUserAccounts() {
const { data: accounts, error } = await this.client
.from('user_accounts')

View File

@@ -16,7 +16,8 @@
"./mfa": "./src/mfa.ts",
"./captcha/client": "./src/captcha/client/index.ts",
"./captcha/server": "./src/captcha/server/index.ts",
"./resend-email-link": "./src/components/resend-auth-link-form.tsx"
"./resend-email-link": "./src/components/resend-auth-link-form.tsx",
"./lib/utils/*": "./src/lib/utils/*.ts"
},
"devDependencies": {
"@hookform/resolvers": "^5.0.1",

View File

@@ -55,14 +55,14 @@ export function SignInMethodsContainer(props: {
}
try {
const { data: hasPersonalCode } = await client.rpc(
'has_personal_code',
const { data: hasConsentPersonalData } = await client.rpc(
'has_consent_personal_data',
{
account_id: userId,
},
);
if (hasPersonalCode) {
if (hasConsentPersonalData) {
router.replace(props.paths.returnPath);
} else {
router.replace(props.paths.updateAccount);

View File

@@ -23,7 +23,7 @@ export interface AccountSubmitData {
}
export const onUpdateAccount = enhanceAction(
async (params) => {
async (params: AccountSubmitData) => {
const client = getSupabaseServerClient();
const api = createAuthApi(client);
@@ -36,7 +36,14 @@ export const onUpdateAccount = enhanceAction(
}
console.warn('On update account error: ', err);
}
redirect(pathsConfig.auth.updateAccountSuccess);
const hasUnseenMembershipConfirmation =
await api.hasUnseenMembershipConfirmation();
if (hasUnseenMembershipConfirmation) {
redirect(pathsConfig.auth.membershipConfirmation);
} else {
redirect(pathsConfig.app.selectPackage);
}
},
{
schema: UpdateAccountSchema,

View File

@@ -13,14 +13,24 @@ class AuthApi {
constructor(private readonly client: SupabaseClient<Database>) {}
/**
* @name hasPersonalCode
* @description Check if given account ID has added personal code.
* @param id
* @name hasUnseenMembershipConfirmation
* @description Check if given user ID has any unseen membership confirmation.
*/
async hasPersonalCode(id: string) {
const { data, error } = await this.client.rpc('has_personal_code', {
account_id: id,
});
async hasUnseenMembershipConfirmation() {
const {
data: { user },
} = await this.client.auth.getUser();
if (!user) {
throw new Error('User not authenticated');
}
const { data, error } = await this.client.rpc(
'has_unseen_membership_confirmation',
{
p_user_id: user.id,
},
);
if (error) {
throw error;

View File

@@ -1,3 +1,2 @@
export * from './notifications-popover';
export * from './success-notification';
export * from './update-account-success-notification';

View File

@@ -1,39 +0,0 @@
'use client';
import { redirect } from 'next/navigation';
import pathsConfig from '@/config/paths.config';
import { useTranslation } from 'react-i18next';
import { usePersonalAccountData } from '@kit/accounts/hooks/use-personal-account-data';
import { SuccessNotification } from './success-notification';
export const UpdateAccountSuccessNotification = ({
userId,
}: {
userId?: string;
}) => {
const { t } = useTranslation('account');
if (!userId) {
redirect(pathsConfig.app.home);
}
const { data: accountData } = usePersonalAccountData(userId);
return (
<SuccessNotification
showLogo={false}
title={t('account:updateAccount:successTitle', {
firstName: accountData?.name,
lastName: accountData?.last_name,
})}
descriptionKey="account:updateAccount:successDescription"
buttonProps={{
buttonTitleKey: 'account:updateAccount:successButton',
href: pathsConfig.app.selectPackage,
}}
/>
);
};

View File

@@ -117,7 +117,10 @@ class AccountInvitationsService {
}
const isUserAlreadyMember = members.find((member) => {
return member.email === invitation.email || member.personal_code === invitation.personal_code;
return (
member.email === invitation.email ||
member.personal_code === invitation.personal_code
);
});
if (isUserAlreadyMember) {

View File

@@ -17,7 +17,7 @@ export type Database = {
changed_data: Json | null
id: number
operation: string
record_key: number | null
record_key: string | null
row_data: Json | null
schema_name: string
table_name: string
@@ -29,7 +29,7 @@ export type Database = {
changed_data?: Json | null
id?: number
operation: string
record_key?: number | null
record_key?: string | null
row_data?: Json | null
schema_name: string
table_name: string
@@ -41,7 +41,7 @@ export type Database = {
changed_data?: Json | null
id?: number
operation?: string
record_key?: number | null
record_key?: string | null
row_data?: Json | null
schema_name?: string
table_name?: string
@@ -252,6 +252,7 @@ export type Database = {
account_role: string
created_at: string
created_by: string | null
has_seen_confirmation: boolean
updated_at: string
updated_by: string | null
user_id: string
@@ -261,6 +262,7 @@ export type Database = {
account_role: string
created_at?: string
created_by?: string | null
has_seen_confirmation?: boolean
updated_at?: string
updated_by?: string | null
user_id: string
@@ -270,6 +272,7 @@ export type Database = {
account_role?: string
created_at?: string
created_by?: string | null
has_seen_confirmation?: boolean
updated_at?: string
updated_by?: string | null
user_id?: string
@@ -901,74 +904,21 @@ export type Database = {
},
]
}
medreport_product_groups: {
Row: {
created_at: string
id: number
name: string
updated_at: string | null
}
Insert: {
created_at?: string
id?: number
name: string
updated_at?: string | null
}
Update: {
created_at?: string
id?: number
name?: string
updated_at?: string | null
}
Relationships: []
}
medreport_products: {
Row: {
created_at: string
id: number
name: string
product_group_id: number | null
updated_at: string | null
}
Insert: {
created_at?: string
id?: number
name: string
product_group_id?: number | null
updated_at?: string | null
}
Update: {
created_at?: string
id?: number
name?: string
product_group_id?: number | null
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "medreport_products_product_groups_id_fkey"
columns: ["product_group_id"]
isOneToOne: false
referencedRelation: "medreport_product_groups"
referencedColumns: ["id"]
},
]
}
medreport_products_analyses_relations: {
medusa_products_analyses_relations: {
Row: {
analysis_element_id: number | null
analysis_id: number | null
product_id: number
medusa_product_id: number
}
Insert: {
analysis_element_id?: number | null
analysis_id?: number | null
product_id: number
medusa_product_id: number
}
Update: {
analysis_element_id?: number | null
analysis_id?: number | null
product_id?: number
medusa_product_id?: number
}
Relationships: [
{
@@ -985,27 +935,20 @@ export type Database = {
referencedRelation: "analyses"
referencedColumns: ["id"]
},
{
foreignKeyName: "medreport_products_analyses_product_id_fkey"
columns: ["product_id"]
isOneToOne: true
referencedRelation: "medreport_products"
referencedColumns: ["id"]
},
]
}
medreport_products_external_services_relations: {
medusa_products_external_services_relations: {
Row: {
connected_online_service_id: number
product_id: number
medusa_product_id: number
}
Insert: {
connected_online_service_id: number
product_id: number
medusa_product_id: number
}
Update: {
connected_online_service_id?: number
product_id?: number
medusa_product_id?: number
}
Relationships: [
{
@@ -1015,13 +958,6 @@ export type Database = {
referencedRelation: "connected_online_services"
referencedColumns: ["id"]
},
{
foreignKeyName: "medreport_products_connected_online_services_product_id_fkey"
columns: ["product_id"]
isOneToOne: false
referencedRelation: "medreport_products"
referencedColumns: ["id"]
},
]
}
nonces: {
@@ -1464,10 +1400,6 @@ export type Database = {
Args: { target_team_account_id: string; target_user_id: string }
Returns: boolean
}
check_personal_code_exists: {
Args: { code: string }
Returns: boolean
}
create_invitation: {
Args: { account_id: string; email: string; role: string }
Returns: {
@@ -1574,6 +1506,10 @@ export type Database = {
Args: { target_account_id: string }
Returns: boolean
}
has_consent_personal_data: {
Args: { account_id: string }
Returns: boolean
}
has_more_elevated_role: {
Args: {
target_user_id: string
@@ -1590,10 +1526,6 @@ export type Database = {
}
Returns: boolean
}
has_personal_code: {
Args: { account_id: string }
Returns: boolean
}
has_role_on_account: {
Args: { account_id: string; account_role?: string }
Returns: boolean
@@ -1606,6 +1538,10 @@ export type Database = {
}
Returns: boolean
}
has_unseen_membership_confirmation: {
Args: { p_user_id?: string }
Returns: boolean
}
is_aal2: {
Args: Record<PropertyKey, never>
Returns: boolean