2.18.0: New Invitation flow, refactored Database Webhooks, new ShadCN UI Components (#384)

* Streamlined invitations flow
* Removed web hooks in favor of handling logic directly in server actions
* Added new Shadcn UI Components
This commit is contained in:
Giancarlo Buomprisco
2025-10-05 17:54:16 +08:00
committed by GitHub
parent 195cf41680
commit 2e20d3e76f
60 changed files with 3760 additions and 1009 deletions

View File

@@ -85,8 +85,10 @@ export const deletePersonalAccountAction = enhanceAction(
// delete the user's account and cancel all subscriptions
await service.deletePersonalAccount({
adminClient: getSupabaseServerAdminClient(),
userId: user.id,
userEmail: user.email ?? null,
account: {
id: user.id,
email: user.email ?? null,
},
});
// sign out the user after deleting their account

View File

@@ -2,6 +2,8 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import { z } from 'zod';
import { getLogger } from '@kit/shared/logger';
import { Database } from '@kit/supabase/database';
@@ -30,13 +32,14 @@ class DeletePersonalAccountService {
*/
async deletePersonalAccount(params: {
adminClient: SupabaseClient<Database>;
userId: string;
userEmail: string | null;
account: {
id: string;
email: string | null;
};
}) {
const logger = await getLogger();
const userId = params.userId;
const userId = params.account.id;
const ctx = { userId, name: this.namespace };
logger.info(
@@ -54,6 +57,14 @@ class DeletePersonalAccountService {
logger.info(ctx, 'User successfully deleted!');
if (params.account.email) {
// dispatch the delete account email. Errors are handled in the method.
await this.dispatchDeleteAccountEmail({
email: params.account.email,
id: params.account.id,
});
}
return {
success: true,
};
@@ -69,4 +80,71 @@ class DeletePersonalAccountService {
throw new Error('Error deleting user');
}
}
private async dispatchDeleteAccountEmail(account: {
email: string;
id: string;
}) {
const logger = await getLogger();
const ctx = { name: this.namespace, userId: account.id };
try {
logger.info(ctx, 'Sending delete account email...');
await this.sendDeleteAccountEmail(account);
logger.info(ctx, 'Delete account email sent successfully');
} catch (error) {
logger.error(
{
...ctx,
error,
},
'Failed to send delete account email',
);
}
}
private async sendDeleteAccountEmail(account: { email: string }) {
const emailSettings = this.getEmailSettings();
const { renderAccountDeleteEmail } = await import('@kit/email-templates');
const { getMailer } = await import('@kit/mailers');
const mailer = await getMailer();
const { html, subject } = await renderAccountDeleteEmail({
productName: emailSettings.productName,
});
await mailer.sendEmail({
from: emailSettings.fromEmail,
html,
subject,
to: account.email,
});
}
private getEmailSettings() {
const productName = process.env.NEXT_PUBLIC_PRODUCT_NAME;
const fromEmail = process.env.EMAIL_SENDER;
return z
.object({
productName: z
.string({
required_error: 'NEXT_PUBLIC_PRODUCT_NAME is required',
})
.min(1),
fromEmail: z
.string({
required_error: 'EMAIL_SENDER is required',
})
.min(1),
})
.parse({
productName,
fromEmail,
});
}
}