'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; import { enhanceAction } from '@kit/next/actions'; import { createNotificationsApi } from '@kit/notifications/api'; import { getLogger } from '@kit/shared/logger'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { AcceptInvitationSchema } from '../../schema/accept-invitation.schema'; import { DeleteInvitationSchema } from '../../schema/delete-invitation.schema'; import { InviteMembersSchema } from '../../schema/invite-members.schema'; import { RenewInvitationSchema } from '../../schema/renew-invitation.schema'; import { UpdateInvitationSchema } from '../../schema/update-invitation.schema'; import { createAccountInvitationsService } from '../services/account-invitations.service'; import { createAccountPerSeatBillingService } from '../services/account-per-seat-billing.service'; /** * @name createInvitationsAction * @description Creates invitations for inviting members. */ export const createInvitationsAction = enhanceAction( async (params) => { const logger = await getLogger(); const client = getSupabaseServerClient(); const serviceClient = getSupabaseServerAdminClient(); const service = createAccountInvitationsService(client); const api = createNotificationsApi(serviceClient); //TODO: This does not send email. It only creates invitations in the database. await service.sendInvitations(params); const { invitations: invitationParams, accountSlug } = params; const personalCodes = invitationParams.map( ({ personal_code }) => personal_code, ); const { data: company, error: companyError } = await client .from('accounts') .select('id') .eq('slug', accountSlug); logger.debug({ company, companyError, personalCodes }); if (companyError || !company?.length || !company[0]) { throw new Error( `Failed to fetch company id: ${companyError?.message || 'not found'}`, ); } const { data: invitations, error: invitationError } = await serviceClient.rpc('get_invitations_with_account_ids', { company_id: company[0].id, personal_codes: personalCodes, }); logger.debug({ invitations, invitationError }); if (invitationError) { throw new Error( `Failed to fetch invitations with accounts: ${invitationError.message}`, ); } const notificationPromises = invitations.map( ({ invite_token, account_id }) => api.createNotification({ account_id: account_id!, body: `You are invited to join the company: ${accountSlug}`, link: `/join?invite_token=${invite_token}`, }), ); await Promise.all(notificationPromises); logger.info('All invitation notifications are sent'); revalidateMemberPage(); return { success: true, }; }, { schema: InviteMembersSchema.and( z.object({ accountSlug: z.string().min(1), }), ), }, ); /** * @name deleteInvitationAction * @description Deletes an invitation specified by the invitation ID. */ export const deleteInvitationAction = enhanceAction( async (data) => { const client = getSupabaseServerClient(); const service = createAccountInvitationsService(client); // Delete the invitation await service.deleteInvitation(data); revalidateMemberPage(); return { success: true, }; }, { schema: DeleteInvitationSchema, }, ); /** * @name updateInvitationAction * @description Updates an invitation. */ export const updateInvitationAction = enhanceAction( async (invitation) => { const client = getSupabaseServerClient(); const service = createAccountInvitationsService(client); await service.updateInvitation(invitation); revalidateMemberPage(); return { success: true, }; }, { schema: UpdateInvitationSchema, }, ); /** * @name acceptInvitationAction * @description Accepts an invitation to join a team. */ export const acceptInvitationAction = enhanceAction( async (data: FormData, user) => { const client = getSupabaseServerClient(); const { inviteToken, nextPath } = AcceptInvitationSchema.parse( Object.fromEntries(data), ); // create the services const perSeatBillingService = createAccountPerSeatBillingService(client); const service = createAccountInvitationsService(client); // use admin client to accept invitation const adminClient = getSupabaseServerAdminClient(); // Accept the invitation const accountId = await service.acceptInvitationToTeam(adminClient, { inviteToken, userId: user.id, }); // If the account ID is not present, throw an error if (!accountId) { throw new Error('Failed to accept invitation'); } // Increase the seats for the account await perSeatBillingService.increaseSeats(accountId); return redirect(nextPath); }, {}, ); /** * @name renewInvitationAction * @description Renews an invitation. */ export const renewInvitationAction = enhanceAction( async (params) => { const client = getSupabaseServerClient(); const { invitationId } = RenewInvitationSchema.parse(params); const service = createAccountInvitationsService(client); // Renew the invitation await service.renewInvitation(invitationId); revalidateMemberPage(); return { success: true, }; }, { schema: RenewInvitationSchema, }, ); function revalidateMemberPage() { revalidatePath('/home/[account]/members', 'page'); }