Working and testing the profile module.

This commit is contained in:
charlesgauthereau
2025-12-23 18:25:31 +01:00
parent d504866aad
commit d19a46bee6
9 changed files with 166 additions and 135 deletions
@@ -0,0 +1,38 @@
import * as React from "react";
import EmailLayout from "../email-layout";
import {Heading, Text, Section, Button} from "@react-email/components";
import {getServerUrl} from "@/utils/get-server-url";
interface EmailCreateUserProps {
firstname?: string;
token: string;
}
export const EmailForgotPassword = ({firstname, token}: EmailCreateUserProps) => {
const baseUrl = getServerUrl();
return (
<EmailLayout preview="Portabase - Forgot Password">
<Heading className="mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black">
Hello <strong>{firstname}</strong>,
</Heading>
<Text className="text-[14px] text-black leading-[24px]">
We received a request to reset the password for your account associated with this email.
</Text>
<Text className="text-[14px] text-black leading-[24px]">
If you did not request this password reset, please ignore this email. Your current password
will remain unchanged. If this seems suspicious, please contact your administrator.
</Text>
<Section className="mt-[32px] mb-[32px] text-center">
<Button
className="rounded bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline"
href={`${baseUrl}/reset-password?token=${token}`}
>
Reset my password
</Button>
</Section>
</EmailLayout>
);
};
export default EmailForgotPassword;
@@ -0,0 +1,32 @@
import * as React from "react";
import EmailLayout from "../email-layout";
import {Heading, Text} from "@react-email/components";
interface EmailCreateUserProps {
firstname?: string;
os: string;
browser: string;
ipAddress?: string;
}
export const EmailNewLogin = ({firstname, ipAddress, os, browser}: EmailCreateUserProps) => {
return (
<EmailLayout preview="Portabase - New Login">
<Heading className="mx-0 my-[30px] p-0 text-center font-normal text-[24px] text-black">
Hello <strong>{firstname}</strong>,
</Heading>
<Text className="text-[14px] text-black leading-[24px]">
We detected a new login to your account.
</Text>
<Text className="text-[14px] text-black leading-[24px]">
<strong>Login details:</strong>
<br/>
Device: {os} - {browser}
<br/>
IP Address: {ipAddress}
</Text>
</EmailLayout>
);
};
export default EmailNewLogin;
@@ -1,30 +0,0 @@
import * as React from "react";
import EmailLayout from "./email-layout";
import {Text, Section, Button} from "@react-email/components";
export interface EmailResetPasswordProps {
url: string;
}
export const EmailResetPassword = ({url}: EmailResetPasswordProps) => {
return (
<EmailLayout preview="Email for password reset of your Portabase account">
<Text className="text-base font-bold ">Hello !</Text>
<Text className="text-base font-light ">You are receiving this email because we
received a password reset request for your account.</Text>{" "}
<Section className="mt-[32px] mb-[32px] text-center">
<Button
className="rounded bg-[#000000] px-5 py-3 text-center font-semibold text-[12px] text-white no-underline"
href={url}
>
Reset Password
</Button>
</Section>
<Text className="text-base font-light ">If you did not request a password reset, no
further action is required.</Text>
<Text className="text-base font-light ">Regards,<br/>Portabase</Text>
</EmailLayout>
);
};
export default EmailResetPassword;
@@ -30,7 +30,7 @@ export const ForgotPasswordForm = (props: ForgotPasswordFormProps) => {
},
{
onSuccess: () => {
toast.success("Password reset successfully.");
toast.success("If an account with this email address exists, you will receive an email with instructions to reset your password.");
},
onError: (error) => {
toast.error(error.error.message);
@@ -63,6 +63,7 @@ export const LoginForm = (props: loginFormProps) => {
toast.success("Login success");
},
onError: (error) => {
console.log(error);
toast.error(error.error.message);
},
}
@@ -1,70 +1,8 @@
// "use server"
// import type {EventPayload, DispatchResult} from '../types';
// import nodemailer from 'nodemailer';
// import TestEmailSettings from "@/components/emails/email-settings-test"
//
// export async function sendSmtp(
// config: {
// host: string;
// port: number;
// secure: boolean;
// user: string;
// password: string;
// from: string;
// to: string | string[];
// },
// payload: EventPayload
// ): Promise<DispatchResult> {
// console.log(config)
// const transporter = nodemailer.createTransport({
// pool: true,
// host: config.host,
// port: config.port,
// // secure: config.secure,
// secure: true,
// auth: {user: config.user, pass: config.password},
// });
//
//
//
// const result = await transporter.verify();
// console.log(result);
//
// const html = `
// <h2>${payload.title}</h2>
// <p><strong>Level:</strong> ${payload.level}</p>
// <p>${payload.message.replace(/\n/g, '<br>')}</p>
// ${payload.data ? `<pre>${JSON.stringify(payload.data, null, 2)}</pre>` : ''}
// `;
//
// const info = await transporter.sendMail({
// from: config.from,
// to: Array.isArray(config.to) ? config.to.join(', ') : config.to,
// // to: config.from,
// subject: `[${payload.level.toUpperCase()}] ${payload.title}`,
// html,
// // subject: "Portabase",
// // html: await render(TestEmailSettings(), {}),
// });
//
// console.log(info);
//
// return {
// success: true,
// provider: 'smtp',
// // message: `Email sent: ${info.messageId}`,
// message: `Email sent: ${config.to}`,
// response: info,
// };
// }
"use server";
import { render } from "@react-email/render";
import nodemailer from "nodemailer";
import type { EventPayload, DispatchResult } from '../types';
import EmailNotification from "@/components/emails/email-notification";
import EmailResetPassword from "@/components/emails/email-reset-password";
export async function sendSmtp(
config: {
+38 -30
View File
@@ -11,10 +11,12 @@ import {count, eq} from "drizzle-orm";
import {MemberWithUser, OrganizationWithMembersAndUsers} from "@/db/schema/03_organization";
import {sendEmail} from "@/lib/email/email-helper";
import {render} from "@react-email/render";
import EmailResetPassword from "@/components/emails/email-reset-password";
import {SUPPORTED_PROVIDERS} from "../../../portabase.config";
import {withUpdatedAt} from "@/db/utils";
import EmailVerification from "@/components/emails/email-verification";
import EmailVerification from "@/components/emails/auth/email-verification";
import EmailForgotPassword from "@/components/emails/auth/email-forgot-password";
import {getDeviceDetails} from "@/utils/detection";
import EmailNewLogin from "@/components/emails/auth/email-new-login";
export const auth = betterAuth({
database: drizzleAdapter(db, {
@@ -27,7 +29,7 @@ export const auth = betterAuth({
emailAndPassword: {
enabled: true,
requireEmailVerification: false,
sendResetPassword: async ({user, url, token}, request) => {
sendResetPassword: async ({user, token}, request) => {
const [updatedUser] = await db.update(drizzleDb.schemas.user).set(withUpdatedAt({
emailVerified: true,
@@ -36,7 +38,13 @@ export const auth = betterAuth({
await sendEmail({
to: user.email,
subject: "Reset your password",
html: await render(EmailResetPassword({url: url})),
html: await render(
EmailForgotPassword({
firstname: user.name!,
token,
}),
{}
),
});
},
@@ -71,8 +79,6 @@ export const auth = betterAuth({
});
},
},
socialProviders: SUPPORTED_PROVIDERS.reduce((acc: any, provider: any) => {
if (provider.id === "credential") return acc;
if (provider.id === "google") {
@@ -89,7 +95,6 @@ export const auth = betterAuth({
},
},
plugins: [
openAPI(),
nextCookies(),
@@ -176,7 +181,6 @@ export const auth = betterAuth({
} else {
console.warn("Default organization not found. Cannot assign member.");
}
},
},
},
@@ -185,38 +189,42 @@ export const auth = betterAuth({
before: async (session, context) => {
const userId = session.userId;
let memberships = await db.query.member.findMany({
where: eq(drizzleDb.schemas.member.userId, userId),
});
// if (!memberships.length) {
// const defaultOrgSlug = "default";
// const defaultOrg = await db.query.organization.findFirst({
// where: eq(drizzleDb.schemas.organization.slug, defaultOrgSlug),
// });
//
// if (!defaultOrg) {
// throw new Error("No organization found. Cannot assign member.");
// }
//
// await db.insert(drizzleDb.schemas.member).values({
// userId,
// organizationId: defaultOrg.id,
// role: "member",
// });
//
// memberships = await db.query.member.findMany({
// where: eq(drizzleDb.schemas.member.userId, userId),
// });
// }
return {
data: {
activeOrganizationId: memberships[0].organizationId,
},
};
},
after: async (session) => {
const user = await db.query.user.findFirst({
where: eq(drizzleDb.schemas.user.id, session.userId),
});
if (user && user.role != "pending") {
const deviceInfo = getDeviceDetails(session.userAgent);
await sendEmail({
to: user.email,
subject: "New login to your account",
html: await render(
EmailNewLogin({
firstname: user.name!,
os: deviceInfo.os,
browser: deviceInfo.browser,
ipAddress: session.ipAddress!,
}),
{}
),
});
(await auth.$context).internalAdapter.updateUser(user.id, {
lastConnectedAt: new Date(),
});
}
},
},
},