mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
feat: demo
This commit is contained in:
48
demo/nextjs/lib/actions.ts
Normal file
48
demo/nextjs/lib/actions.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
"use server"
|
||||
import { APIError, createEndpoint } from "better-call"
|
||||
import { z } from "zod"
|
||||
import { sessionMiddleware } from "./middleware"
|
||||
import { db } from "./db"
|
||||
import { TodoStatus } from "@prisma/client"
|
||||
|
||||
export const createTodo = createEndpoint("/todo/create", {
|
||||
method: "POST",
|
||||
body: z.object({
|
||||
title: z.string(),
|
||||
status: z.nativeEnum(TodoStatus),
|
||||
assignedTo: z.string().optional(),
|
||||
}),
|
||||
use: [sessionMiddleware]
|
||||
}, async (ctx) => {
|
||||
const { session } = ctx.context
|
||||
try {
|
||||
const newTodo = await db.todo.create({
|
||||
data: {
|
||||
title: ctx.body.title,
|
||||
description: "",
|
||||
status: ctx.body.status,
|
||||
createdById: session.user.id,
|
||||
assignToId: session.session.activeOrganizationId ? ctx.body.assignedTo : undefined,
|
||||
organizationId: session.session.activeOrganizationId,
|
||||
},
|
||||
include: {
|
||||
createdBy: true,
|
||||
organization: true,
|
||||
assignTo: true
|
||||
}
|
||||
}).then(res => {
|
||||
return {
|
||||
...res,
|
||||
assignToId: res.assignTo?.name || "",
|
||||
createdById: res.createdBy?.name || "",
|
||||
}
|
||||
})
|
||||
if (!newTodo) {
|
||||
throw new APIError("INTERNAL_SERVER_ERROR")
|
||||
}
|
||||
return ctx.json(newTodo)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
throw new APIError("INTERNAL_SERVER_ERROR")
|
||||
}
|
||||
})
|
||||
11
demo/nextjs/lib/auth-client.ts
Normal file
11
demo/nextjs/lib/auth-client.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { organizationClient, passkeyClient, twoFactorClient } from "better-auth/client/plugins"
|
||||
|
||||
export const client = createAuthClient({
|
||||
plugins: [organizationClient(), twoFactorClient(), passkeyClient()],
|
||||
fetchOptions: {
|
||||
credentials: 'include'
|
||||
}
|
||||
});
|
||||
|
||||
export const { signUp, signIn, signOut, useSession, user, organization, useListOrganizations, useActiveOrganization } = client;
|
||||
6
demo/nextjs/lib/auth-types.ts
Normal file
6
demo/nextjs/lib/auth-types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { auth } from "./auth";
|
||||
import { organization } from "./auth-client";
|
||||
|
||||
export type Session = typeof auth['$infer']['session']
|
||||
export type ActiveOrganization = typeof organization.$Infer.ActiveOrganization
|
||||
export type Invitation = typeof organization.$Infer.Invitation
|
||||
47
demo/nextjs/lib/auth.ts
Normal file
47
demo/nextjs/lib/auth.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { organization, passkey, twoFactor } from "better-auth/plugins";
|
||||
import { Resend } from "resend";
|
||||
import { reactInvitationEmail } from "./email/invitation";
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
const from = process.env.BETTER_AUTH_EMAIL || "delivered@resend.dev";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: {
|
||||
provider: "sqlite",
|
||||
url: "./auth.db",
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
plugins: [
|
||||
organization({
|
||||
async sendInvitationEmail(data) {
|
||||
const res = await resend.emails.send({
|
||||
from,
|
||||
to: data.email,
|
||||
subject: "You've been invited to join an organization",
|
||||
react: reactInvitationEmail({
|
||||
username: data.email,
|
||||
invitedByUsername: data.inviter.user.name,
|
||||
invitedByEmail: data.inviter.user.email,
|
||||
teamName: data.organization.name,
|
||||
inviteLink:
|
||||
process.env.NODE_ENV === "development"
|
||||
? `http://localhost:3000/accept-invitation/${data.id}`
|
||||
: `https://${
|
||||
process.env.NEXT_PUBLIC_APP_URL ||
|
||||
process.env.VERCEL_URL ||
|
||||
process.env.BETTER_AUTH_URL
|
||||
}/accept-invitation/${data.id}`,
|
||||
}),
|
||||
});
|
||||
console.log(res, data.email);
|
||||
},
|
||||
}),
|
||||
twoFactor(),
|
||||
passkey({
|
||||
rpID: "localhost",
|
||||
}),
|
||||
],
|
||||
});
|
||||
6
demo/nextjs/lib/client.ts
Normal file
6
demo/nextjs/lib/client.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createClient } from "better-call/client";
|
||||
import { router } from "./router";
|
||||
|
||||
export const client = createClient<typeof router>({
|
||||
baseURL: "http://localhost:3000/api/v1",
|
||||
});
|
||||
110
demo/nextjs/lib/email/invitation.tsx
Normal file
110
demo/nextjs/lib/email/invitation.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
Body,
|
||||
Button,
|
||||
Container,
|
||||
Column,
|
||||
Head,
|
||||
Heading,
|
||||
Hr,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Row,
|
||||
Section,
|
||||
Text,
|
||||
Tailwind,
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
|
||||
interface BetterAuthInviteUserEmailProps {
|
||||
username?: string;
|
||||
invitedByUsername?: string;
|
||||
invitedByEmail?: string;
|
||||
teamName?: string;
|
||||
teamImage?: string;
|
||||
inviteLink?: string;
|
||||
}
|
||||
|
||||
|
||||
export const InviteUserEmail = ({
|
||||
username,
|
||||
invitedByUsername,
|
||||
invitedByEmail,
|
||||
teamName,
|
||||
teamImage,
|
||||
inviteLink,
|
||||
}: BetterAuthInviteUserEmailProps) => {
|
||||
const previewText = `Join ${invitedByUsername} on BetterAuth`;
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind>
|
||||
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||
<Heading className="text-black text-[24px] font-normal text-center p-0 my-[30px] mx-0">
|
||||
Join <strong>{invitedByUsername}</strong> on <strong>Better Auth.</strong>
|
||||
</Heading>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
Hello there,
|
||||
</Text>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
<strong>{invitedByUsername}</strong> (
|
||||
<Link
|
||||
href={`mailto:${invitedByEmail}`}
|
||||
className="text-blue-600 no-underline"
|
||||
>
|
||||
{invitedByEmail}
|
||||
</Link>
|
||||
) has invited you to the <strong>{teamName}</strong> team on{" "}
|
||||
<strong>Better Auth</strong>.
|
||||
</Text>
|
||||
<Section>
|
||||
{
|
||||
teamImage ? <Row>
|
||||
<Column align="left">
|
||||
<Img
|
||||
className="rounded-full"
|
||||
src={teamImage}
|
||||
width="64"
|
||||
height="64"
|
||||
fetchPriority="high"
|
||||
/>
|
||||
</Column>
|
||||
</Row> : null
|
||||
}
|
||||
</Section>
|
||||
<Section className="text-center mt-[32px] mb-[32px]">
|
||||
<Button
|
||||
className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3"
|
||||
href={inviteLink}
|
||||
>
|
||||
Join the team
|
||||
</Button>
|
||||
</Section>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
or copy and paste this URL into your browser:{" "}
|
||||
<Link href={inviteLink} className="text-blue-600 no-underline">
|
||||
{inviteLink}
|
||||
</Link>
|
||||
</Text>
|
||||
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
|
||||
<Text className="text-[#666666] text-[12px] leading-[24px]">
|
||||
This invitation was intended for{" "}
|
||||
<span className="text-black">{username}</span>. If you
|
||||
were not expecting this invitation, you can ignore this email.
|
||||
</Text>
|
||||
</Container>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
export function reactInvitationEmail(props: BetterAuthInviteUserEmailProps) {
|
||||
console.log(props)
|
||||
return <InviteUserEmail {...props} />
|
||||
}
|
||||
0
demo/nextjs/lib/middleware.ts
Normal file
0
demo/nextjs/lib/middleware.ts
Normal file
12
demo/nextjs/lib/router.ts
Normal file
12
demo/nextjs/lib/router.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createRouter } from "better-call";
|
||||
import { createTodo } from "./actions";
|
||||
|
||||
|
||||
export const router = createRouter({
|
||||
createTodo,
|
||||
}, {
|
||||
basePath: "/api/v1",
|
||||
onError(e) {
|
||||
console.log(e)
|
||||
},
|
||||
})
|
||||
6
demo/nextjs/lib/utils.ts
Normal file
6
demo/nextjs/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
Reference in New Issue
Block a user