Skip to content

Commit 7734f9a

Browse files
committed
perf(misc): derive twoFA inline; lazy-init Resend client
Closes #89. apps/web/src/app/(app)/dashboard/security/page.tsx — drop the useState+useEffect that synced twoFA from session.user.twoFactorEnabled. Derived inline so it tracks the source of truth without an extra render. Also drop the optimistic setTwoFA in the Switch's onCheckedChange (the wizard target route is the real flip path). packages/email/src/lib/client.ts — Resend client is now a lazy singleton behind a Proxy. Importing @starter-saas/email no longer instantiates Resend at module load, so RESEND_API_KEY can be absent in environments that don't actually send mail; the first send call throws a clear error if the key is missing.
1 parent 7bc901a commit 7734f9a

2 files changed

Lines changed: 30 additions & 11 deletions

File tree

apps/web/src/app/(app)/dashboard/security/page.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,14 @@ type SessionRow = {
3737

3838
export default function SecurityPage() {
3939
const { data } = authClient.useSession();
40-
const [twoFA, setTwoFA] = useState(false);
40+
// Derive during render — Better Auth's useSession is the source of
41+
// truth. The wizard at /security/2fa flips this server-side and a
42+
// session refresh propagates the new value.
43+
const twoFA = Boolean(
44+
(data?.user as { twoFactorEnabled?: boolean })?.twoFactorEnabled,
45+
);
4146
const [sessions, setSessions] = useState<SessionRow[] | null>(null);
4247

43-
useEffect(() => {
44-
setTwoFA(
45-
Boolean((data?.user as { twoFactorEnabled?: boolean })?.twoFactorEnabled),
46-
);
47-
}, [data]);
48-
4948
useEffect(() => {
5049
(async () => {
5150
try {
@@ -94,13 +93,12 @@ export default function SecurityPage() {
9493
<Switch
9594
id="2fa"
9695
checked={twoFA}
97-
onCheckedChange={async (next) => {
96+
onCheckedChange={(next) => {
9897
toast.info(
9998
next
10099
? "2FA setup flow — wire to /security/2fa wizard"
101100
: "Disabling 2FA — wire confirmation modal",
102101
);
103-
setTwoFA(next);
104102
}}
105103
/>
106104
</CardContent>

packages/email/src/lib/client.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,28 @@ import { env } from "@starter-saas/env/server";
22
import type { ReactElement } from "react";
33
import { Resend } from "resend";
44

5-
export const resend = new Resend(env.RESEND_API_KEY);
5+
// Lazy singleton — instantiating Resend at module load means any caller
6+
// that imports `@starter-saas/email` pays the cost (and crashes if the
7+
// key is absent) even when no email is actually sent. Defer to first use.
8+
let _resend: Resend | null = null;
9+
function getResend(): Resend {
10+
if (!_resend) {
11+
if (!env.RESEND_API_KEY) {
12+
throw new Error("RESEND_API_KEY is not set");
13+
}
14+
_resend = new Resend(env.RESEND_API_KEY);
15+
}
16+
return _resend;
17+
}
18+
19+
// Kept as an export for direct callers (webhooks etc.) — same lazy path.
20+
export const resend = new Proxy({} as Resend, {
21+
get(_target, prop) {
22+
const r = getResend();
23+
const value = (r as unknown as Record<string | symbol, unknown>)[prop];
24+
return typeof value === "function" ? (value as () => unknown).bind(r) : value;
25+
},
26+
});
627

728
export type SendEmailInput = {
829
to: string | string[];
@@ -19,7 +40,7 @@ export async function sendEmail({
1940
from,
2041
replyTo,
2142
}: SendEmailInput) {
22-
const { data, error } = await resend.emails.send({
43+
const { data, error } = await getResend().emails.send({
2344
from: from ?? env.EMAIL_FROM,
2445
to,
2546
subject,

0 commit comments

Comments
 (0)