feat: demo

This commit is contained in:
Bereket Engida
2024-09-16 13:53:41 +03:00
parent 1452de923f
commit c945d1ba95
104 changed files with 20586 additions and 227 deletions

View 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")
}
})

View 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;

View 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
View 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",
}),
],
});

View 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",
});

View 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} />
}

View File

12
demo/nextjs/lib/router.ts Normal file
View 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
View 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))
}