Imported from financialvice/realtime-rrweb-recording (
apps/web/src/app/(public)/login/AGENTS.md). Install upstream withnpx skills add financialvice/realtime-rrweb-recording --skill login. Copyright stays with the author.
src/app/(public)/login/ - Authentication
Overview
Magic link authentication page using InstantDB. Users enter email to receive 6-digit code, then sign in.
File Structure
login/
└── page.tsx # Two-step auth UI (email → code)
Features
Two-Step Authentication Flow
-
Email Step
- User enters email address
- Calls
db.auth.sendMagicCode({ email }) - Code sent to email inbox
-
Code Step
- User enters 6-digit code
- Calls
db.auth.signInWithMagicCode({ email, code }) - Auto-submit when 6 digits entered
- Resend code option
- Back button to re-enter email
Code Pattern
"use client";
import { db } from "@repo/db";
export default function LoginPage() {
return (
<>
<db.SignedOut>
<Login />
</db.SignedOut>
<db.RedirectSignedIn onRedirect={() => router.push("/")} />
</>
);
}
function EmailStep({ onSendEmail }) {
const handleSubmit = (e) => {
e.preventDefault();
const email = inputRef.current.value;
db.auth.sendMagicCode({ email }).catch((err) => {
alert(`Error: ${err.body?.message}`);
});
onSendEmail(email);
};
return <form onSubmit={handleSubmit}>{/* form UI */}</form>;
}
function CodeStep({ sentEmail, onBack }) {
const handleSubmit = (e) => {
e.preventDefault();
db.auth.signInWithMagicCode({
email: sentEmail,
code: value
}).catch((err) => {
alert(`Error: ${err.body?.message}`);
});
};
return <form onSubmit={handleSubmit}>{/* code UI */}</form>;
}
Key Hooks & Functions
db.auth Primitives
-
db.auth.sendMagicCode({ email: string })- Sends 6-digit code to email
- Returns promise
-
db.auth.signInWithMagicCode({ email: string, code: string })- Verifies code and signs in user
- Returns promise
- Throws on invalid code
Conditional Rendering
<db.SignedOut>- Only show login if not authenticated<db.RedirectSignedIn>- Redirect to home if already signed in
UI Components
- Card - Layout container
- Input - Email/code inputs
- InputOTP - 6-digit code input component
- Button - Submit, resend, back buttons
Error Handling
db.auth.sendMagicCode({ email })
.catch((err) => {
const message = err.body?.message || "Unknown error";
alert(`Uh oh: ${message}`);
onSendEmail(""); // Reset
});
Important Notes
- Email must be valid (basic validation)
- Code expires after certain time (handled by InstantDB)
- Codes are 6 digits (alphanumeric)
- Already-signed-in users skip to home
- No external auth provider required (magic link only)
- Error messages from InstantDB are user-friendly
Common Tasks
Add Email Validation
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
if (!isValidEmail) {
alert("Invalid email address");
return;
}
Add Loading States
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
try {
await db.auth.sendMagicCode({ email });
onSendEmail(email);
} finally {
setIsLoading(false);
}
};
Customize UI
- Change layout (left side image, full width, etc.)
- Modify card styling
- Adjust form inputs and buttons
- Add header/footer with app branding
Testing
Use any email address in development:
- Email doesn't need to exist
- Code always works in dev mode
- Check InstantDB console for test emails