Initial push

This commit is contained in:
2026-03-08 15:49:34 +01:00
parent 8da12bb7d1
commit 47127f276d
101 changed files with 13844 additions and 8 deletions

53
lib/email/index.ts Normal file
View File

@@ -0,0 +1,53 @@
import { Resend } from "resend";
import { render } from "@react-email/render";
import { InvitationEmail } from "./templates/invitation";
function getResend(): Resend {
const key = process.env.RESEND_API_KEY;
if (!key) throw new Error("RESEND_API_KEY is not configured");
return new Resend(key);
}
export async function sendMagicLinkEmail({
email,
url,
token: _token,
}: {
email: string;
url: string;
token: string;
}): Promise<void> {
const { error } = await getResend().emails.send({
from: process.env.EMAIL_FROM ?? "CubeAdmin <noreply@example.com>",
to: email,
subject: "Your CubeAdmin sign-in link",
html: `<p>Click the link below to sign in to CubeAdmin. This link expires in 1 hour.</p><p><a href="${url}">${url}</a></p>`,
});
if (error) throw new Error(`Failed to send magic link email: ${error.message}`);
}
export async function sendInvitationEmail({
to,
invitedByName,
inviteUrl,
role,
}: {
to: string;
invitedByName: string;
inviteUrl: string;
role: string;
}): Promise<void> {
const html = await render(
InvitationEmail({ invitedByName, inviteUrl, role }),
);
const { error } = await getResend().emails.send({
from: process.env.EMAIL_FROM ?? "CubeAdmin <noreply@example.com>",
to,
subject: `You've been invited to CubeAdmin`,
html,
});
if (error) throw new Error(`Failed to send email: ${error.message}`);
}

View File

@@ -0,0 +1,78 @@
import {
Body,
Button,
Container,
Head,
Heading,
Html,
Preview,
Section,
Text,
Tailwind,
} from "@react-email/components";
import * as React from "react";
interface InvitationEmailProps {
invitedByName: string;
inviteUrl: string;
role: string;
}
export function InvitationEmail({
invitedByName,
inviteUrl,
role,
}: InvitationEmailProps) {
return (
<Html>
<Head />
<Preview>
{invitedByName} invited you to manage a Minecraft server on CubeAdmin
</Preview>
<Tailwind>
<Body className="bg-zinc-950 font-sans">
<Container className="mx-auto max-w-lg py-12 px-6">
{/* Logo */}
<Section className="mb-8 text-center">
<Heading className="text-2xl font-bold text-emerald-500 m-0">
CubeAdmin
</Heading>
</Section>
{/* Card */}
<Section className="bg-zinc-900 rounded-xl border border-zinc-800 p-8">
<Heading className="text-xl font-semibold text-white mt-0 mb-2">
You&apos;ve been invited
</Heading>
<Text className="text-zinc-400 mt-0 mb-6">
<strong className="text-white">{invitedByName}</strong> has
invited you to join CubeAdmin as a{" "}
<strong className="text-emerald-400">{role}</strong>. Click the
button below to create your account and start managing the
Minecraft server.
</Text>
<Button
href={inviteUrl}
className="bg-emerald-600 hover:bg-emerald-500 text-white font-semibold rounded-lg px-6 py-3 text-sm no-underline inline-block"
>
Accept Invitation
</Button>
<Text className="text-zinc-500 text-xs mt-6 mb-0">
This invitation link expires in 48 hours. If you didn&apos;t
expect this email, you can safely ignore it.
</Text>
</Section>
<Text className="text-zinc-600 text-xs text-center mt-6">
CubeAdmin Minecraft Server Management
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
}
export default InvitationEmail;