fix: tests

This commit is contained in:
Bereket Engida
2024-09-02 22:55:01 +03:00
parent 2bee49947a
commit e1f934884e
36 changed files with 2035 additions and 278 deletions

View File

@@ -3,7 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev --port 3000", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",

View File

@@ -1,79 +0,0 @@
<script lang="ts">
import { client } from "$lib/client";
const session = client.$session;
</script>
<div
style="display: flex; min-height: 100vh; align-items: center; justify-content: center;"
>
<div
style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
>
<div>
{#if $session}
<div>
<p>
{$session?.user.name}
</p>
<p>
{$session?.user.email}
</p>
<button
on:click={async () => {
await client.signOut();
}}
>
Signout
</button>
</div>
{:else}
<button
on:click={async () => {
await client.signIn.social({
provider: "github",
});
}}
>
Continue with github
</button>
{/if}
</div>
</div>
</div>
<style>
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
</style>

View File

@@ -0,0 +1,3 @@
export async function load(request: Request) {
return null;
}

View File

@@ -0,0 +1,5 @@
<script lang="ts">
import { client } from "$lib/client";
export let data;
</script>

View File

@@ -0,0 +1,19 @@
import { auth } from "$lib/auth";
export async function load(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return {
status: 401,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
error: "Unauthorized",
}),
};
}
return session;
}

View File

@@ -3,4 +3,7 @@ import { defineConfig } from "vite";
export default defineConfig({ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit()],
optimizeDeps: {
exclude: ["@node-rs/argon2", "@node-rs/bcrypt"],
},
}); });

View File

@@ -3,17 +3,19 @@ import { } from "better-auth/client";
import Section from '@/components/landing/section'; import Section from '@/components/landing/section';
import Hero from '@/components/landing/hero'; import Hero from '@/components/landing/hero';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { FeaturesSectionDemo } from '@/components/blocks/features-section-demo-3';
export default function HomePage() { export default function HomePage() {
return ( return (
<Section <main>
className="-z-1 mb-1" <Section
crosses className="-z-1 mb-1"
crossesOffset="lg:translate-y-[5.25rem]" crosses
customPaddings crossesOffset="lg:translate-y-[5.25rem]"
id="hero" customPaddings
> id="hero"
<Hero /> >
<Separator className="w-full" /> <Hero />
</Section> </Section>
</main>
); );
} }

View File

@@ -0,0 +1,129 @@
import React from "react";
import { useId } from "react";
export function FeaturesSectionDemo() {
return (
<div className="py-2">
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-10 md:gap-2 max-w-7xl mx-auto">
{grid.map((feature) => (
<div
key={feature.title}
className="relative bg-gradient-to-b dark:from-neutral-900 from-neutral-100 dark:to-neutral-950 to-white p-6 overflow-hidden"
>
<Grid size={20} />
<p className="text-base font-bold text-neutral-800 dark:text-white relative z-20">
{feature.title}
</p>
<p className="text-neutral-600 dark:text-neutral-400 mt-4 text-base font-normal relative z-20">
{feature.description}
</p>
</div>
))}
</div>
</div>
);
}
const grid = [
{
title: "Framework Agnostic",
description:
"Supports your favorite frontend, backend and meta frameworks, including React, Vue, Svelte, Solid, Next.js, Nuxt.js, Hono, and more.",
},
{
title: "Email & Password Authentication",
description:
"Builtin support for email and password authentication, with secure password hashing and account management features.",
},
{
title: "Social Sign-on",
description:
"Allow users to sign in with their accounts, including Github, Google, Discord, Twitter, and more.",
},
{
title: "Two Factor",
description:
"Add an extra layer of security with two-factor authentication, including TOTP and SMS.",
},
{
title: "Organization & Access Control",
description:
"Manage users and their access to resources within your application.",
},
{
title: "Plugin Ecosystem",
description:
"Enhance your application with our official plugins and those created by the community.",
},
];
export const Grid = ({
pattern,
size,
}: {
pattern?: number[][];
size?: number;
}) => {
const p = pattern ?? [
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
[Math.floor(Math.random() * 4) + 7, Math.floor(Math.random() * 6) + 1],
];
return (
<div className="pointer-events-none absolute left-1/2 top-0 -ml-20 -mt-2 h-full w-full [mask-image:linear-gradient(white,transparent)]">
<div className="absolute inset-0 bg-gradient-to-r [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-zinc-900/30 from-zinc-100/30 to-zinc-300/30 dark:to-zinc-900/30 opacity-100">
<GridPattern
width={size ?? 20}
height={size ?? 20}
x="-12"
y="4"
squares={p}
className="absolute inset-0 h-full w-full mix-blend-overlay dark:fill-white/10 dark:stroke-white/10 stroke-black/10 fill-black/10"
/>
</div>
</div>
);
};
export function GridPattern({ width, height, x, y, squares, ...props }: any) {
const patternId = useId();
return (
<svg aria-hidden="true" {...props}>
<defs>
<pattern
id={patternId}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path d={`M.5 ${height}V.5H${width}`} fill="none" />
</pattern>
</defs>
<rect
width="100%"
height="100%"
strokeWidth={0}
fill={`url(#${patternId})`}
/>
{squares && (
<svg x={x} y={y} className="overflow-visible">
{squares.map(([x, y]: any) => (
<rect
strokeWidth="0"
key={`${x}-${y}`}
width={width + 1}
height={height + 1}
x={x * width}
y={y * height}
/>
))}
</svg>
)}
</svg>
);
}

View File

@@ -9,6 +9,7 @@ import { Highlight, themes } from "prism-react-renderer";
import { Fragment, useEffect, useId, useState } from "react"; import { Fragment, useEffect, useId, useState } from "react";
import { LayoutGroup, motion } from "framer-motion" import { LayoutGroup, motion } from "framer-motion"
import { Icons } from "../icons"; import { Icons } from "../icons";
import { Cover } from "../ui/cover";
function Glow() { function Glow() {
@@ -92,146 +93,148 @@ export default function Hero() {
return ( return (
<section className="flex mt-1 min-h-screen items-center justify-center gap-20 p-5"> <section className="flex mt-1 min-h-screen items-center justify-center gap-20 p-5">
<div className="overflow-hidden bg-transparent dark:-mb-32 dark:mt-[-4.75rem] dark:pb-32 dark:pt-[4.75rem] px-10"> <div className="overflow-hidden bg-transparent dark:-mb-32 dark:mt-[-4.75rem] dark:pb-32 dark:pt-[4.75rem] md:px-10">
<div className="py-16 sm:px-2 lg:relative lg:px-20 lg:py-20"> <div className="grid max-w-full mx-auto grid-cols-1 items-center gap-x-8 gap-y-16 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12">
<div className="grid max-w-full mx-auto grid-cols-1 items-center gap-x-8 gap-y-16 px-4 lg:max-w-8xl lg:grid-cols-2 lg:px-8 xl:gap-x-16 xl:px-12"> <div className="relative z-10 md:text-center lg:text-left">
<div className="relative z-10 md:text-center lg:text-left"> <div className="relative">
<div className="relative"> <div className="flex items-center gap-2 relative">
<div className="flex items-center gap-2 relative"> <Cover>
<p className="inline dark:text-white opacity-90 text-5xl tracking-tight relative"> <p className="inline dark:text-white opacity-90 2xl md:text-3xl lg:text-5xl tracking-tight relative">
BETTER-AUTH. Better Auth.
</p> </p>
</div> </Cover>
</div>
<p className="mt-3 text-2xl font-mono tracking-tight dark:text-zinc-300 text-zinc-800"> <p className="mt-3 md:text-2xl font-mono tracking-tight dark:text-zinc-300 text-zinc-800">
Framework agnostic, comprehensive authentication library and ecosystem for typescript. Framework agnostic, comprehensive authentication solution with a plugin ecosystem for typescript.
</p> </p>
<div className="mt-8 flex gap-4 font-sans md:justify-center lg:justify-start flex-col md:flex-row">
<button className="px-4 md:px-8 py-0.5 border-2 border-black dark:border-white uppercase bg-white text-black transition duration-200 text-sm shadow-[1px_1px_rgba(0,0,0),2px_2px_rgba(0,0,0),3px_3px_rgba(0,0,0),4px_4px_rgba(0,0,0),5px_5px_0px_0px_rgba(0,0,0)] dark:shadow-[1px_1px_rgba(255,255,255),2px_2px_rgba(255,255,255),3px_3px_rgba(255,255,255),4px_4px_rgba(255,255,255),5px_5px_0px_0px_rgba(255,255,255)] ">
Get Started
</button>
<Button variant="outline" size="lg" className="flex rounded-none items-center gap-2">
<Github size={16} />
View on GitHub
</Button>
<div className="mt-8 flex gap-4 font-sans md:justify-center lg:justify-start">
<Link href="/docs">
<Button variant="default">Get started</Button>
</Link>
<Button variant="outline" className="flex items-center gap-2">
<Github size={16} />
View on GitHub
</Button>
</div>
</div> </div>
</div> </div>
</div>
<div className="relative lg:static xl:pl-10"> <div className="relative lg:static xl:pl-10 hidden md:block">
<div className="relative"> <div className="relative">
{/* <div className="absolute inset-0 rounded-none bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-5 blur-lg" /> {/* <div className="absolute inset-0 rounded-none bg-gradient-to-tr from-sky-300 via-sky-300/70 to-blue-300 opacity-5 blur-lg" />
<div className="absolute inset-0 rounded-none bg-gradient-to-tr from-stone-300 via-stone-300/70 to-blue-300 opacity-5" /> */} <div className="absolute inset-0 rounded-none bg-gradient-to-tr from-stone-300 via-stone-300/70 to-blue-300 opacity-5" /> */}
<LayoutGroup > <LayoutGroup >
<motion.div <motion.div
layoutId="hero" layoutId="hero"
className="relative rounded-sm bg-gradient-to-tr from-stone-100 to-stone-200 dark:from-stone-950/70 dark:to-stone-900/90 ring-1 ring-white/10 backdrop-blur-lg"> className="relative rounded-sm bg-gradient-to-tr from-stone-100 to-stone-200 dark:from-stone-950/70 dark:to-stone-900/90 ring-1 ring-white/10 backdrop-blur-lg">
<div className="absolute -top-px left-0 right-0 h-px " /> <div className="absolute -top-px left-0 right-0 h-px " />
<div className="absolute -bottom-px left-11 right-20 h-px" /> <div className="absolute -bottom-px left-11 right-20 h-px" />
<div className="pl-4 pt-4"> <div className="pl-4 pt-4">
<TrafficLightsIcon className="h-2.5 w-auto stroke-slate-500/30" /> <TrafficLightsIcon className="h-2.5 w-auto stroke-slate-500/30" />
<div className="mt-4 flex space-x-2 text-xs"> <div className="mt-4 flex space-x-2 text-xs">
{tabs.map((tab) => ( {tabs.map((tab) => (
<motion.div <motion.div
key={tab.name} key={tab.name}
layoutId={`tab-${tab.name}`} layoutId={`tab-${tab.name}`}
whileHover={{ whileHover={{
scale: 1.05, scale: 1.05,
transition: { duration: 1 }, transition: { duration: 1 },
}} }}
whileTap={{ scale: 0.1 }} whileTap={{ scale: 0.1 }}
onClick={() => setActiveTab(tab.name)} onClick={() => setActiveTab(tab.name)}
className={clsx(
"flex h-6 rounded-full cursor-pointer",
activeTab === tab.name
? "bg-gradient-to-r from-stone-400/90 via-stone-400 to-orange-400/20 p-px font-medium text-stone-300"
: "text-slate-500",
)}
>
<div
className={clsx( className={clsx(
"flex h-6 rounded-full cursor-pointer", "flex items-center rounded-full px-2.5",
activeTab === tab.name tab.name === activeTab && "bg-stone-800",
? "bg-gradient-to-r from-stone-400/90 via-stone-400 to-orange-400/20 p-px font-medium text-stone-300"
: "text-slate-500",
)} )}
> >
<div {tab.name}
className={clsx( </div>
"flex items-center rounded-full px-2.5", </motion.div>
tab.name === activeTab && "bg-stone-800", ))}
)}
>
{tab.name}
</div>
</motion.div>
))}
</div>
<div className="mt-6 flex items-start px-1 text-sm">
<div
aria-hidden="true"
className="select-none border-r border-slate-300/5 pr-4 font-mono text-slate-600"
>
{Array.from({
length: code.split("\n").length,
}).map((_, index) => (
<Fragment key={index}>
{(index + 1).toString().padStart(2, "0")}
<br />
</Fragment>
))}
</div>
<Highlight
key={theme.resolvedTheme}
code={code}
language={"javascript"}
theme={{
...themes.synthwave84,
plain: {
backgroundColor: "transparent",
}
}
}
>
{({
className,
style,
tokens,
getLineProps,
getTokenProps,
}) => (
<pre
className={clsx(
className,
"flex overflow-x-auto pb-6",
)}
style={style}
>
<code className="px-4">
{tokens.map((line, lineIndex) => (
<div
key={lineIndex}
{...getLineProps({ line })}
>
{line.map((token, tokenIndex) => (
<span
key={tokenIndex}
{...getTokenProps({ token })}
/>
))}
</div>
))}
</code>
</pre>
)}
</Highlight>
</div>
</div> </div>
</motion.div>
</LayoutGroup> <div className="mt-6 flex items-start px-1 text-sm">
</div> <div
aria-hidden="true"
className="select-none border-r border-slate-300/5 pr-4 font-mono text-slate-600"
>
{Array.from({
length: code.split("\n").length,
}).map((_, index) => (
<Fragment key={index}>
{(index + 1).toString().padStart(2, "0")}
<br />
</Fragment>
))}
</div>
<Highlight
key={theme.resolvedTheme}
code={code}
language={"javascript"}
theme={{
...themes.synthwave84,
plain: {
backgroundColor: "transparent",
}
}
}
>
{({
className,
style,
tokens,
getLineProps,
getTokenProps,
}) => (
<pre
className={clsx(
className,
"flex overflow-x-auto pb-6",
)}
style={style}
>
<code className="px-4">
{tokens.map((line, lineIndex) => (
<div
key={lineIndex}
{...getLineProps({ line })}
>
{line.map((token, tokenIndex) => (
<span
key={tokenIndex}
{...getTokenProps({ token })}
/>
))}
</div>
))}
</code>
</pre>
)}
</Highlight>
</div>
</div>
</motion.div>
</LayoutGroup>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -260,11 +260,6 @@ export const contents: Content[] = [
href: "/docs/integrations", href: "/docs/integrations",
icon: LucideAArrowDown icon: LucideAArrowDown
}, },
{
title: "Hono",
icon: Icons.hono,
href: "/docs/integrations/hono",
},
{ {
title: "Next", title: "Next",
icon: Icons.nextJS, icon: Icons.nextJS,
@@ -280,16 +275,16 @@ export const contents: Content[] = [
icon: Icons.svelteKit, icon: Icons.svelteKit,
href: "/docs/integrations/svelte-kit", href: "/docs/integrations/svelte-kit",
}, },
{
title: "Hono",
icon: Icons.hono,
href: "/docs/integrations/hono",
},
{ {
title: "Solid Start", title: "Solid Start",
icon: Icons.solidStart, icon: Icons.solidStart,
href: "/docs/integrations/solid-start", href: "/docs/integrations/solid-start",
}, },
{
title: "React",
icon: Icons.react,
href: "/docs/integrations/react",
},
] ]
}, },
{ {
@@ -311,13 +306,7 @@ export const contents: Content[] = [
</svg> </svg>
), ),
list: [ list: [
{
title: "Introduction",
icon: () => (
<svg xmlns="http://www.w3.org/2000/svg" width="1.1em" height="1.1em" viewBox="0 0 14 14"><path fill="currentColor" fillRule="evenodd" d="M12.402 8.976H7.259a2.278 2.278 0 0 0-.193-4.547h-1.68A3.095 3.095 0 0 0 4.609 0h7.793a1.35 1.35 0 0 1 1.348 1.35v6.279c0 .744-.604 1.348-1.348 1.348ZM2.898 4.431a1.848 1.848 0 1 0 0-3.695a1.848 1.848 0 0 0 0 3.695m5.195 2.276c0-.568-.46-1.028-1.027-1.028H2.899a2.65 2.65 0 0 0-2.65 2.65v1.205c0 .532.432.963.964.963h.172l.282 2.61A1 1 0 0 0 2.66 14h.502a1 1 0 0 0 .99-.862l.753-5.404h2.16c.567 0 1.027-.46 1.027-1.027Z" clipRule="evenodd"></path></svg>
),
href: "/docs/plugins/introduction",
},
{ {
title: "Authentication", title: "Authentication",
group: true, group: true,
@@ -361,12 +350,7 @@ export const contents: Content[] = [
{ {
title: "Bearer", title: "Bearer",
icon: Key, icon: Key,
href: "/docs/plugins/email-verifier", href: "/docs/plugins/bearer",
},
{
title: "Email Checker",
icon: MailCheck,
href: "/docs/plugins/email-verifier",
}, },
], ],
}, },

View File

@@ -0,0 +1,141 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
export const BackgroundBeams = React.memo(
({ className }: { className?: string }) => {
const paths = [
"M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875",
"M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867",
"M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859",
"M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851",
"M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843",
"M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835",
"M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827",
"M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819",
"M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811",
"M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803",
"M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795",
"M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787",
"M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779",
"M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771",
"M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763",
"M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755",
"M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747",
"M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739",
"M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731",
"M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723",
"M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715",
"M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707",
"M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699",
"M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691",
"M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683",
"M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675",
"M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667",
"M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659",
"M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651",
"M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643",
"M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635",
"M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627",
"M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619",
"M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611",
"M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603",
"M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595",
"M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587",
"M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579",
"M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571",
"M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563",
"M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555",
"M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547",
"M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539",
"M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531",
"M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523",
"M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515",
"M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507",
"M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499",
"M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491",
"M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483",
];
return (
<div
className={cn(
"absolute h-full w-full inset-0 [mask-size:40px] [mask-repeat:no-repeat] flex items-center justify-center",
className
)}
>
<svg
className=" z-0 h-full w-full pointer-events-none absolute "
width="100%"
height="100%"
viewBox="0 0 696 316"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875M-373 -197C-373 -197 -305 208 159 335C623 462 691 867 691 867M-366 -205C-366 -205 -298 200 166 327C630 454 698 859 698 859M-359 -213C-359 -213 -291 192 173 319C637 446 705 851 705 851M-352 -221C-352 -221 -284 184 180 311C644 438 712 843 712 843M-345 -229C-345 -229 -277 176 187 303C651 430 719 835 719 835M-338 -237C-338 -237 -270 168 194 295C658 422 726 827 726 827M-331 -245C-331 -245 -263 160 201 287C665 414 733 819 733 819M-324 -253C-324 -253 -256 152 208 279C672 406 740 811 740 811M-317 -261C-317 -261 -249 144 215 271C679 398 747 803 747 803M-310 -269C-310 -269 -242 136 222 263C686 390 754 795 754 795M-303 -277C-303 -277 -235 128 229 255C693 382 761 787 761 787M-296 -285C-296 -285 -228 120 236 247C700 374 768 779 768 779M-289 -293C-289 -293 -221 112 243 239C707 366 775 771 775 771M-282 -301C-282 -301 -214 104 250 231C714 358 782 763 782 763M-275 -309C-275 -309 -207 96 257 223C721 350 789 755 789 755M-268 -317C-268 -317 -200 88 264 215C728 342 796 747 796 747M-261 -325C-261 -325 -193 80 271 207C735 334 803 739 803 739M-254 -333C-254 -333 -186 72 278 199C742 326 810 731 810 731M-247 -341C-247 -341 -179 64 285 191C749 318 817 723 817 723M-240 -349C-240 -349 -172 56 292 183C756 310 824 715 824 715M-233 -357C-233 -357 -165 48 299 175C763 302 831 707 831 707M-226 -365C-226 -365 -158 40 306 167C770 294 838 699 838 699M-219 -373C-219 -373 -151 32 313 159C777 286 845 691 845 691M-212 -381C-212 -381 -144 24 320 151C784 278 852 683 852 683M-205 -389C-205 -389 -137 16 327 143C791 270 859 675 859 675M-198 -397C-198 -397 -130 8 334 135C798 262 866 667 866 667M-191 -405C-191 -405 -123 0 341 127C805 254 873 659 873 659M-184 -413C-184 -413 -116 -8 348 119C812 246 880 651 880 651M-177 -421C-177 -421 -109 -16 355 111C819 238 887 643 887 643M-170 -429C-170 -429 -102 -24 362 103C826 230 894 635 894 635M-163 -437C-163 -437 -95 -32 369 95C833 222 901 627 901 627M-156 -445C-156 -445 -88 -40 376 87C840 214 908 619 908 619M-149 -453C-149 -453 -81 -48 383 79C847 206 915 611 915 611M-142 -461C-142 -461 -74 -56 390 71C854 198 922 603 922 603M-135 -469C-135 -469 -67 -64 397 63C861 190 929 595 929 595M-128 -477C-128 -477 -60 -72 404 55C868 182 936 587 936 587M-121 -485C-121 -485 -53 -80 411 47C875 174 943 579 943 579M-114 -493C-114 -493 -46 -88 418 39C882 166 950 571 950 571M-107 -501C-107 -501 -39 -96 425 31C889 158 957 563 957 563M-100 -509C-100 -509 -32 -104 432 23C896 150 964 555 964 555M-93 -517C-93 -517 -25 -112 439 15C903 142 971 547 971 547M-86 -525C-86 -525 -18 -120 446 7C910 134 978 539 978 539M-79 -533C-79 -533 -11 -128 453 -1C917 126 985 531 985 531M-72 -541C-72 -541 -4 -136 460 -9C924 118 992 523 992 523M-65 -549C-65 -549 3 -144 467 -17C931 110 999 515 999 515M-58 -557C-58 -557 10 -152 474 -25C938 102 1006 507 1006 507M-51 -565C-51 -565 17 -160 481 -33C945 94 1013 499 1013 499M-44 -573C-44 -573 24 -168 488 -41C952 86 1020 491 1020 491M-37 -581C-37 -581 31 -176 495 -49C959 78 1027 483 1027 483M-30 -589C-30 -589 38 -184 502 -57C966 70 1034 475 1034 475M-23 -597C-23 -597 45 -192 509 -65C973 62 1041 467 1041 467M-16 -605C-16 -605 52 -200 516 -73C980 54 1048 459 1048 459M-9 -613C-9 -613 59 -208 523 -81C987 46 1055 451 1055 451M-2 -621C-2 -621 66 -216 530 -89C994 38 1062 443 1062 443M5 -629C5 -629 73 -224 537 -97C1001 30 1069 435 1069 435M12 -637C12 -637 80 -232 544 -105C1008 22 1076 427 1076 427M19 -645C19 -645 87 -240 551 -113C1015 14 1083 419 1083 419"
stroke="url(#paint0_radial_242_278)"
strokeOpacity="0.05"
strokeWidth="0.5"
></path>
{paths.map((path, index) => (
<motion.path
key={`path-` + index}
d={path}
stroke={`url(#linearGradient-${index})`}
strokeOpacity="0.4"
strokeWidth="0.5"
></motion.path>
))}
<defs>
{paths.map((path, index) => (
<motion.linearGradient
id={`linearGradient-${index}`}
key={`gradient-${index}`}
initial={{
x1: "0%",
x2: "0%",
y1: "0%",
y2: "0%",
}}
animate={{
x1: ["0%", "100%"],
x2: ["0%", "95%"],
y1: ["0%", "100%"],
y2: ["0%", `${93 + Math.random() * 8}%`],
}}
transition={{
duration: Math.random() * 10 + 10,
ease: "easeInOut",
repeat: Infinity,
delay: Math.random() * 10,
}}
>
<stop stopColor="#18CCFC" stopOpacity="0"></stop>
<stop stopColor="#18CCFC"></stop>
<stop offset="32.5%" stopColor="#6344F5"></stop>
<stop offset="100%" stopColor="#AE48FF" stopOpacity="0"></stop>
</motion.linearGradient>
))}
<radialGradient
id="paint0_radial_242_278"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(352 34) rotate(90) scale(555 1560.62)"
>
<stop offset="0.0666667" stopColor="var(--neutral-300)"></stop>
<stop offset="0.243243" stopColor="var(--neutral-300)"></stop>
<stop offset="0.43594" stopColor="white" stopOpacity="0"></stop>
</radialGradient>
</defs>
</svg>
</div>
);
}
);
BackgroundBeams.displayName = "BackgroundBeams";

View File

@@ -0,0 +1,76 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
export const BoxesCore = ({ className, ...rest }: { className?: string }) => {
const rows = new Array(150).fill(1);
const cols = new Array(100).fill(1);
let colors = [
"--sky-300",
"--pink-300",
"--green-300",
"--yellow-300",
"--red-300",
"--purple-300",
"--blue-300",
"--indigo-300",
"--violet-300",
];
const getRandomColor = () => {
return colors[Math.floor(Math.random() * colors.length)];
};
return (
<div
style={{
transform: `translate(-40%,-60%) skewX(-48deg) skewY(14deg) scale(0.675) rotate(0deg) translateZ(0)`,
}}
className={cn(
"absolute left-1/4 p-4 -top-1/4 flex -translate-x-1/2 -translate-y-1/2 w-full h-full z-0 ",
className
)}
{...rest}
>
{rows.map((_, i) => (
<motion.div
key={`row` + i}
className="w-16 h-8 border-l border-slate-700 relative"
>
{cols.map((_, j) => (
<motion.div
whileHover={{
backgroundColor: `var(${getRandomColor()})`,
transition: { duration: 0 },
}}
animate={{
transition: { duration: 2 },
}}
key={`col` + j}
className="w-16 h-8 border-r border-t border-slate-700 relative"
>
{j % 2 === 0 && i % 2 === 0 ? (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="absolute h-6 w-10 -top-[14px] -left-[22px] text-slate-700 stroke-[1px] pointer-events-none"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 6v12m6-6H6"
/>
</svg>
) : null}
</motion.div>
))}
</motion.div>
))}
</div>
);
};
export const Boxes = React.memo(BoxesCore);

View File

@@ -0,0 +1,52 @@
"use client";
import React, { useEffect, useId, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { useRef } from "react";
import { cn } from "@/lib/utils";
import { SparklesCore } from "@/components/ui/sparkles";
export const Cover = ({
children,
className,
}: {
children?: React.ReactNode;
className?: string;
}) => {
return (
<div
className="relative hover:bg-neutral-900 group/cover inline-block dark:bg-neutral-900 bg-neutral-100 px-4 py-2 transition duration-200 rounded-sm"
>
<span
className={cn(
"dark:text-white inline-block text-neutral-900 relative z-20 group-hover/cover:text-white transition duration-200",
className
)}
>
{children}
</span>
<CircleIcon className="absolute -right-[2px] -top-[2px]" />
<CircleIcon className="absolute -bottom-[2px] -right-[2px]" delay={0.4} />
<CircleIcon className="absolute -left-[2px] -top-[2px]" delay={0.8} />
<CircleIcon className="absolute -bottom-[2px] -left-[2px]" delay={1.6} />
</div>
);
};
export const CircleIcon = ({
className,
delay,
}: {
className?: string;
delay?: number;
}) => {
return (
<div
className={cn(
`pointer-events-none animate-pulse group-hover/cover:hidden group-hover/cover:opacity-100 group h-2 w-2 rounded-full bg-neutral-600 dark:bg-white opacity-20 group-hover/cover:bg-white`,
className
)}
></div>
);
};

View File

@@ -0,0 +1,435 @@
"use client";
import React, { useId, useMemo } from "react";
import { useEffect, useState } from "react";
import Particles, { initParticlesEngine } from "@tsparticles/react";
import type { Container, SingleOrMultiple } from "@tsparticles/engine";
import { loadSlim } from "@tsparticles/slim";
import { cn } from "@/lib/utils";
import { motion, useAnimation } from "framer-motion";
type ParticlesProps = {
id?: string;
className?: string;
background?: string;
particleSize?: number;
minSize?: number;
maxSize?: number;
speed?: number;
particleColor?: string;
particleDensity?: number;
};
export const SparklesCore = (props: ParticlesProps) => {
const {
id,
className,
background,
minSize,
maxSize,
speed,
particleColor,
particleDensity,
} = props;
const [init, setInit] = useState(false);
useEffect(() => {
initParticlesEngine(async (engine) => {
await loadSlim(engine);
}).then(() => {
setInit(true);
});
}, []);
const controls = useAnimation();
const particlesLoaded = async (container?: Container) => {
if (container) {
console.log(container);
controls.start({
opacity: 1,
transition: {
duration: 1,
},
});
}
};
const generatedId = useId();
return (
<motion.div animate={controls} className={cn("opacity-0", className)}>
{init && (
<Particles
id={id || generatedId}
className={cn("h-full w-full")}
particlesLoaded={particlesLoaded}
options={{
background: {
color: {
value: background || "#0d47a1",
},
},
fullScreen: {
enable: false,
zIndex: 1,
},
fpsLimit: 120,
interactivity: {
events: {
onClick: {
enable: true,
mode: "push",
},
onHover: {
enable: false,
mode: "repulse",
},
resize: true as any,
},
modes: {
push: {
quantity: 4,
},
repulse: {
distance: 200,
duration: 0.4,
},
},
},
particles: {
bounce: {
horizontal: {
value: 1,
},
vertical: {
value: 1,
},
},
collisions: {
absorb: {
speed: 2,
},
bounce: {
horizontal: {
value: 1,
},
vertical: {
value: 1,
},
},
enable: false,
maxSpeed: 50,
mode: "bounce",
overlap: {
enable: true,
retries: 0,
},
},
color: {
value: particleColor || "#ffffff",
animation: {
h: {
count: 0,
enable: false,
speed: 1,
decay: 0,
delay: 0,
sync: true,
offset: 0,
},
s: {
count: 0,
enable: false,
speed: 1,
decay: 0,
delay: 0,
sync: true,
offset: 0,
},
l: {
count: 0,
enable: false,
speed: 1,
decay: 0,
delay: 0,
sync: true,
offset: 0,
},
},
},
effect: {
close: true,
fill: true,
options: {},
type: {} as SingleOrMultiple<string> | undefined,
},
groups: {},
move: {
angle: {
offset: 0,
value: 90,
},
attract: {
distance: 200,
enable: false,
rotate: {
x: 3000,
y: 3000,
},
},
center: {
x: 50,
y: 50,
mode: "percent",
radius: 0,
},
decay: 0,
distance: {},
direction: "none",
drift: 0,
enable: true,
gravity: {
acceleration: 9.81,
enable: false,
inverse: false,
maxSpeed: 50,
},
path: {
clamp: true,
delay: {
value: 0,
},
enable: false,
options: {},
},
outModes: {
default: "out",
},
random: false,
size: false,
speed: {
min: 0.1,
max: 1,
},
spin: {
acceleration: 0,
enable: false,
},
straight: false,
trail: {
enable: false,
length: 10,
fill: {},
},
vibrate: false,
warp: false,
},
number: {
density: {
enable: true,
width: 400,
height: 400,
},
limit: {
mode: "delete",
value: 0,
},
value: particleDensity || 120,
},
opacity: {
value: {
min: 0.1,
max: 1,
},
animation: {
count: 0,
enable: true,
speed: speed || 4,
decay: 0,
delay: 0,
sync: false,
mode: "auto",
startValue: "random",
destroy: "none",
},
},
reduceDuplicates: false,
shadow: {
blur: 0,
color: {
value: "#000",
},
enable: false,
offset: {
x: 0,
y: 0,
},
},
shape: {
close: true,
fill: true,
options: {},
type: "circle",
},
size: {
value: {
min: minSize || 1,
max: maxSize || 3,
},
animation: {
count: 0,
enable: false,
speed: 5,
decay: 0,
delay: 0,
sync: false,
mode: "auto",
startValue: "random",
destroy: "none",
},
},
stroke: {
width: 0,
},
zIndex: {
value: 0,
opacityRate: 1,
sizeRate: 1,
velocityRate: 1,
},
destroy: {
bounds: {},
mode: "none",
split: {
count: 1,
factor: {
value: 3,
},
rate: {
value: {
min: 4,
max: 9,
},
},
sizeOffset: true,
},
},
roll: {
darken: {
enable: false,
value: 0,
},
enable: false,
enlighten: {
enable: false,
value: 0,
},
mode: "vertical",
speed: 25,
},
tilt: {
value: 0,
animation: {
enable: false,
speed: 0,
decay: 0,
sync: false,
},
direction: "clockwise",
enable: false,
},
twinkle: {
lines: {
enable: false,
frequency: 0.05,
opacity: 1,
},
particles: {
enable: false,
frequency: 0.05,
opacity: 1,
},
},
wobble: {
distance: 5,
enable: false,
speed: {
angle: 50,
move: 10,
},
},
life: {
count: 0,
delay: {
value: 0,
sync: false,
},
duration: {
value: 0,
sync: false,
},
},
rotate: {
value: 0,
animation: {
enable: false,
speed: 0,
decay: 0,
sync: false,
},
direction: "clockwise",
path: false,
},
orbit: {
animation: {
count: 0,
enable: false,
speed: 1,
decay: 0,
delay: 0,
sync: false,
},
enable: false,
opacity: 1,
rotation: {
value: 45,
},
width: 1,
},
links: {
blink: false,
color: {
value: "#fff",
},
consent: false,
distance: 100,
enable: false,
frequency: 1,
opacity: 1,
shadow: {
blur: 5,
color: {
value: "#000",
},
enable: false,
},
triangles: {
enable: false,
frequency: 1,
},
width: 1,
warp: false,
},
repulse: {
value: 0,
enabled: false,
distance: 1,
duration: 1,
factor: 1,
speed: 1,
},
},
detectRetina: true,
}}
/>
)}
</motion.div>
);
};

View File

@@ -0,0 +1,73 @@
---
title: Hono
description: Hono Integration Guide
---
This integration guide is assuming you are using Hono with node server.
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to hono endpoint.
```ts
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/**", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
```

View File

@@ -0,0 +1,157 @@
---
title: Next JS integration
description: Learn how to integrate Better Auth with Next.js
---
Better Auth can be easily integrated with Next.js. It'll also comes with utilities to make it easier to use Better Auth with Next.js.
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Create API Route
We need to mount the handler to an API route. Create a route file inside `/api/[...auth]` directory. And add the following code:
```ts twoslash title="api/[...auth]/route.ts"
//@filename: @/lib/auth.ts
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
// ---cut---
//@filename: api/[...auth]/route.ts
//---cut---
import { handler } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(handler);
```
<Callout type="info">
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/[...auth]`
</Callout>
### Migrate the database
Run the following command to create the necessary tables in your database:
```bash
npx better-auth migrate
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts twoslash title="client.ts"
import { createAuthClient } from "better-auth/react" // make sure to import from better-auth/react
export const client = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and re-render the components when the state changes.
The client also uses [better-fetch](https://github.com/bekacru/better-fetch) to make the requests. You can pass the fetch configuration to the client.
## RSC and Server actions
The `api` object exported from the auth instance contains all the actions that you can perform on the server. Every endpoint made inside better auth is a invokable as a function. Including plugins endpoints.
**Example: Getting Session on a server action**
```tsx twoslash title="server.ts"
//@filename: @/lib/auth.ts
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
// ---cut---
//@filename: server.ts
//---cut---
import { api } from "@/lib/auth"
import { headers } from "next/headers"
const someAuthenticatedAction = async () => {
"use server";
const session = await api.getSession({
headers: headers()
})
};
```
**Example: Getting Session on a RSC**
```tsx
import { api } from "@/lib/auth"
import { headers } from "next/headers"
export async function ServerComponent() {
const session = await api.getSession({
headers: headers()
})
if(!session) {
return <div>Not authenticated</div>
}
return (
<div>
<h1>Welcome {session.user.name}</h1>
</div>
)
}
```

View File

@@ -0,0 +1,111 @@
---
title: Nuxt.js
description: Learn how to integrate Better Auth with Nuxt.js
---
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Create API Route
We need to mount the handler to an API route. Create a file inside `/server/api` called `[...auth].ts` and add the following code:
```ts title="server/api/[...auth].ts"
import { handler } from "~/utils/auth.config";
export default defineEventHandler((event) => {
return handler(toWebRequest(event));
});
```
<Callout type="info">
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/[...auth]`
</Callout>
### Migrate the database
Run the following command to create the necessary tables in your database:
```bash
npx better-auth migrate
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts twoslash title="client.ts"
import { createAuthClient } from "better-auth/vue" // make sure to import from better-auth/vue
export const client = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and re-render the components when the state changes.
### Example usage
```vue title="index.vue"
<template>
<div style="">
<button v-if="!client.useSession().value" @click="() => client.signIn.social({
provider: 'github'
})">
Continue with github
</button>
<div>
<pre>{{ client.useSession().value }}</pre>
<button v-if="client.useSession().value" @click="client.signOut()">
Sign out
</button>
</div>
</div>
</template>
```

View File

@@ -0,0 +1,64 @@
---
title: Solid Start
description: Solid Start integratons guide
---
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to Solid Start server. Put the following code in your `*auth.ts` file inside `/routes/api/auth` folder.
```ts title="*auth.ts"
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";
export const { GET, POST } = toSolidStartHandler(auth);
```

View File

@@ -0,0 +1,148 @@
---
title: Svelte Kit
description: Learn how to integrate Better Auth with Svelte Kit
---
Better Auth has first class support for Svelte Kit. It provides utilities to make it easier to use Better Auth with Svelte Kit.
## Installation
First, install Better Auth
```package-install
npm install better-auth
```
## Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
**Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 # Base URL of your Next.js app
```
**Set Secret**
Random value used by the library for encryption and generating hashes. You can generate one using the button below or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
<GenerateSecret/>
## Configure Server
### Create Better Auth instance
We recommend to create `auth.ts` file inside your `lib/` directory. This file will contain your Better Auth instance.
```ts twoslash title="auth.ts"
import { betterAuth } from "better-auth"
export const { api, handler } = betterAuth({
database: {
provider: "sqlite", //change this to your database provider
url: "./db.sqlite", // path to your database or connection string
}
// Refer to the api documentation for more configuration options
})
```
<Callout type="warn">
Better Auth currently supports only SQLite, MySQL, and PostgreSQL. It uses Kysely under the hood, so you can also pass any Kysely dialect directly to the database object.
</Callout>
### Mount the handler
We need to mount the handler to svelte kit server hook.
```ts
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}
```
### Migrate the database
Run the following command to create the necessary tables in your database:
```bash
npx better-auth migrate
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts twoslash title="client.ts"
import { createAuthClient } from "better-auth/svelte" // make sure to import from better-auth/svlete
export const client = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actinos are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and refelct changes when there is a change like a user signing in or out affecting the session state.
### Example usage
```svelte
<script lang="ts">
import { client } from "$lib/client";
const session = client.$session;
</script>
<div>
{#if $session}
<div>
<p>
{$session?.user.name}
</p>
<button
on:click={async () => {
await client.signOut();
}}
>
Signout
</button>
</div>
{:else}
<button
on:click={async () => {
await client.signIn.social({
provider: "github",
});
}}
>
Continue with github
</button>
{/if}
</div>
```
### Example: Getting Session on a ServerComponent
```ts title="+page.ts"
import { auth } from "$lib/auth";
export async function load(request: Request) {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session) {
return {
status: 401,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
error: "Unauthorized",
}),
};
}
return session;
}
```

View File

@@ -3,8 +3,16 @@ title: Introduction
description: Introduction to Better Auth. description: Introduction to Better Auth.
--- ---
Better Auth is comprehnsive, batteries included, framework-agnostic and extensible auth library and auth ecosystem for typescript. Better Auth is a type-safe, framework-agnostic authentication library for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities with minimal code. Whether you need 2FA, multi-tenant support, or other complex features. It lets you focus on building your app instead of reinventing the wheel.
## Why Better Auth? ## Why Better Auth?
Auth libraries in the TypeScript ecosystem typically handle basic authentication but often require significant additional code for any additional functionality. Better Auth addresses this gap by providing a comprehensive set of features out of the box and supporting easy extension through plugins for any additional auth-related requirements. Its designed to be framework-agnostic, allowing integration with any framework backend or frontend, and it's built with typesaftey in mind. *Hi there, Im <Link href="https://x.com/imbereket">Beka</Link>, the creator of Better Auth. Ive often found that working with TypeScript auth libraries means dealing with a ton of additional code for anything beyond basic authentication, which is just a huge time sink. I thought we could do better than pushing third-party providers, so I built Better Auth.*
## Features
Better auth is aims to be the most comprehensive auth library. It provides a wide range of features out of the box and allows you to extend it with plugins. Here are some of the features:
<Features/>
and much more...

View File

@@ -0,0 +1,21 @@
---
title: Bearer
description: The Bearer plugin allows you to authenticate with a Bearer token instead of a browser cookie.
---
The Bearer plugin allows you to authenticate with a Bearer token instead of a browser cookie. It proxies the request and set the bearer token in the Authorization header to a cookie request internally.
## Add the Bearer plugin
```ts title="auth.ts" twoslash
import { betterAuth } from "better-auth";
import { bearer } from "better-auth/plugins";
export const { api, handler } = betterAuth({
database: {
provider: 'sqlite',
url: './db.sqlite'
},
plugins: [bearer()]
});
```

View File

@@ -1,10 +0,0 @@
---
title: Introduction
description: Introduction to better auth plugins
---
Better Auth comes with a plugin system. It provides a set of built-in plugins that you can use to authenticate users using different methods. You can also create your own custom plugins to extend the functionality of Better Auth.
## What are plugins?
Plugins can be used to extend the functionality of Better Auth. They allow you to add new features or modify existing features of Better Auth. Plugins can be used to authenticate users using different methods, store user data in a databases, hook into existing authentication methods, and more.

View File

@@ -25,8 +25,6 @@ Organizations Plugin offer a versatile and scalable approach to controlling user
] // [!code highlight] ] // [!code highlight]
}) })
``` ```
Now you have all the endpoints that are required to manage organizations and members. You can call them on the serer using the `api` object. Let's see how to use them on the client.
</Step> </Step>
<Step> <Step>
### Migarate database ### Migarate database

View File

@@ -7,6 +7,7 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { GenerateSecret } from './components/generate-secret'; import { GenerateSecret } from './components/generate-secret';
import { Popup, PopupContent, PopupTrigger } from "fumadocs-ui/twoslash/popup"; import { Popup, PopupContent, PopupTrigger } from "fumadocs-ui/twoslash/popup";
import { TypeTable } from 'fumadocs-ui/components/type-table'; import { TypeTable } from 'fumadocs-ui/components/type-table';
import { FeaturesSectionDemo } from './components/blocks/features-section-demo-3';
export function useMDXComponents(components: MDXComponents): MDXComponents { export function useMDXComponents(components: MDXComponents): MDXComponents {
return { return {
...defaultComponents, ...defaultComponents,
@@ -26,5 +27,6 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
PopupTrigger, PopupTrigger,
PopupContent, PopupContent,
TypeTable, TypeTable,
Features: FeaturesSectionDemo
}; };
} }

View File

@@ -43,6 +43,16 @@ const config = {
}, },
]; ];
}, },
images: {
remotePatterns: [
{
hostname: 'images.unsplash.com'
},
{
hostname: 'assets.aceternity.com'
},
]
}
}; };
export default withMDX(config); export default withMDX(config);

View File

@@ -37,10 +37,15 @@
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@tabler/icons-react": "^3.12.0",
"@tsparticles/engine": "^3.5.0",
"@tsparticles/react": "^3.0.0",
"@tsparticles/slim": "^3.5.0",
"better-auth": "workspace:0.0.2-beta.8", "better-auth": "workspace:0.0.2-beta.8",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "1.0.0", "cmdk": "1.0.0",
"cobe": "^0.6.3",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"embla-carousel-react": "^8.2.0", "embla-carousel-react": "^8.2.0",
"framer-motion": "^11.3.30", "framer-motion": "^11.3.30",
@@ -52,6 +57,7 @@
"geist": "^1.3.1", "geist": "^1.3.1",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lucide-react": "^0.435.0", "lucide-react": "^0.435.0",
"mini-svg-data-uri": "^1.4.4",
"next": "^14.2.5", "next": "^14.2.5",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"prism-react-renderer": "^2.4.0", "prism-react-renderer": "^2.4.0",
@@ -63,7 +69,7 @@
"recharts": "^2.12.7", "recharts": "^2.12.7",
"rehype-mermaid": "^2.1.0", "rehype-mermaid": "^2.1.0",
"sonner": "^1.5.0", "sonner": "^1.5.0",
"tailwind-merge": "^2.5.0", "tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"unist-util-visit": "^5.0.0", "unist-util-visit": "^5.0.0",
"vaul": "^0.9.1", "vaul": "^0.9.1",

View File

@@ -1,5 +1,11 @@
import { createPreset } from 'fumadocs-ui/tailwind-plugin'; import { createPreset } from 'fumadocs-ui/tailwind-plugin';
import defaultTheme from "tailwindcss/defaultTheme"; import defaultTheme from "tailwindcss/defaultTheme";
const colors = require("tailwindcss/colors");
const {
default: flattenColorPalette,
} = require("tailwindcss/lib/util/flattenColorPalette");
const svgToDataUri = require("mini-svg-data-uri");
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
@@ -12,7 +18,28 @@ export default {
'./node_modules/fumadocs-ui/dist/**/*.js', './node_modules/fumadocs-ui/dist/**/*.js',
], ],
presets: [createPreset()], presets: [createPreset()],
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate"), addVariablesForColors, function ({ matchUtilities, theme }) {
matchUtilities(
{
"bg-grid": (value) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
)}")`,
}),
"bg-grid-small": (value) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="8" height="8" fill="none" stroke="${value}"><path d="M0 .5H31.5V32"/></svg>`
)}")`,
}),
"bg-dot": (value) => ({
backgroundImage: `url("${svgToDataUri(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="16" height="16" fill="none"><circle fill="${value}" id="pattern-circle" cx="10" cy="10" r="1.6257413380501518"></circle></svg>`
)}")`,
}),
},
{ values: flattenColorPalette(theme("backgroundColor")), type: "color" }
);
}],
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
@@ -90,5 +117,17 @@ export default {
'accordion-up': 'accordion-up 0.2s ease-out' 'accordion-up': 'accordion-up 0.2s ease-out'
} }
} }
} },
}; };
function addVariablesForColors({ addBase, theme }) {
let allColors = flattenColorPalette(theme("colors"));
let newVars = Object.fromEntries(
Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
);
addBase({
":root": newVars,
});
}

View File

@@ -189,7 +189,7 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
}); });
}, },
onError(e) { onError(e) {
// console.log(e); console.log(e);
}, },
}); });
}; };

View File

@@ -16,7 +16,7 @@ const testCredential2 = {
describe("credential", async () => { describe("credential", async () => {
it("should sign up with email and password", async () => { it("should sign up with email and password", async () => {
const res = await client.signUp.credential({ const res = await client.signUp.email({
email: testCredential1.email, email: testCredential1.email,
password: testCredential1.password, password: testCredential1.password,
name: testCredential1.name, name: testCredential1.name,
@@ -39,7 +39,7 @@ describe("credential", async () => {
}, },
}); });
const res2 = await auth.api.signUpCredential({ const res2 = await auth.api.signUpEmail({
method: "POST", method: "POST",
body: { body: {
email: testCredential2.email, email: testCredential2.email,
@@ -69,7 +69,7 @@ describe("credential", async () => {
describe("sign-in credential", async () => { describe("sign-in credential", async () => {
it("should sign in with email and password", async () => { it("should sign in with email and password", async () => {
const res = await client.signIn.credential({ const res = await client.signIn.email({
email: testCredential1.email, email: testCredential1.email,
password: testCredential1.password, password: testCredential1.password,
}); });
@@ -91,7 +91,7 @@ describe("sign-in credential", async () => {
}, },
}); });
const res2 = await auth.api.signInCredential({ const res2 = await auth.api.signInEmail({
method: "POST", method: "POST",
body: { body: {
email: testCredential2.email, email: testCredential2.email,
@@ -121,7 +121,7 @@ describe("sign-in credential", async () => {
}); });
it("should't remember me", async () => { it("should't remember me", async () => {
const res = await client.signIn.credential({ const res = await client.signIn.email({
email: testCredential1.email, email: testCredential1.email,
password: testCredential1.password, password: testCredential1.password,
dontRememberMe: true, dontRememberMe: true,

View File

@@ -9,12 +9,15 @@ export const betterAuth = <O extends BetterAuthOptions>(options: O) => {
type PluginEndpoint = UnionToIntersection< type PluginEndpoint = UnionToIntersection<
O["plugins"] extends Array<infer T> O["plugins"] extends Array<infer T>
? T extends BetterAuthPlugin ? T extends BetterAuthPlugin
? T["endpoints"] ? {
[key in T["id"]]: T["endpoints"];
}
: {} : {}
: {} : {}
>; >;
const { handler, endpoints } = router(authContext); const { handler, endpoints } = router(authContext);
type Endpoint = typeof endpoints; type Endpoint = typeof endpoints;
return { return {
handler, handler,
api: endpoints as Endpoint & PluginEndpoint, api: endpoints as Endpoint & PluginEndpoint,

View File

@@ -154,7 +154,9 @@ export async function getMigrations(config: BetterAuthOptions) {
if (toBeCreated.length) { if (toBeCreated.length) {
for (const table of toBeCreated) { for (const table of toBeCreated) {
let dbT = db.schema.createTable(table.table); let dbT = db.schema
.createTable(table.table)
.addColumn("id", "text", (col) => col.primaryKey());
for (const [fieldName, field] of Object.entries(table.fields)) { for (const [fieldName, field] of Object.entries(table.fields)) {
const type = typeMap[field.type]; const type = typeMap[field.type];
dbT = dbT.addColumn(fieldName, type, (col) => { dbT = dbT.addColumn(fieldName, type, (col) => {

View File

@@ -1,21 +1,13 @@
import { describe, it } from "vitest"; import { describe, it } from "vitest";
import { getTestInstance } from "../test-utils/test-instance"; import { getTestInstance } from "../test-utils/test-instance";
import { createAuthClient } from "./base";
import { createAuthClient as createReactClient } from "./react"; import { createAuthClient as createReactClient } from "./react";
import { twoFactorClient } from "../plugins"; import { organization } from "../plugins";
import { usernameClient } from "../plugins/username/client";
import { organizationClient } from "./plugins"; import { organizationClient } from "./plugins";
import { createAccessControl } from "../plugins/organization/access"; import { createAccessControl } from "../plugins/organization/access";
describe("client path to object", async () => { describe("client path to object", async () => {
const auth = await getTestInstance({ const auth = await getTestInstance({
plugins: [ plugins: [organization()],
// passkey({
// rpID: "test",
// rpName: "test",
// }),
],
}); });
it("should return a path to object", async () => { it("should return a path to object", async () => {
@@ -41,6 +33,5 @@ describe("client path to object", async () => {
], ],
csrfPlugin: false, csrfPlugin: false,
}); });
client.organization.useActiveOrganization();
}); });
}); });

View File

@@ -6,8 +6,8 @@ describe("bearer", async () => {
const { client, createTestUser } = await getTestInstance({ const { client, createTestUser } = await getTestInstance({
plugins: [bearer()], plugins: [bearer()],
}); });
await createTestUser();
it("should get session", async () => { it("should get session", async () => {
await createTestUser();
const res = await client.$fetch<{ session: { id: string } }>( const res = await client.$fetch<{ session: { id: string } }>(
"/sign-in/credential", "/sign-in/credential",
{ {

View File

@@ -5,6 +5,7 @@ import { betterAuth } from "../auth";
import { createAuthClient } from "../client"; import { createAuthClient } from "../client";
import { github, google } from "../social-providers"; import { github, google } from "../social-providers";
import type { BetterAuthOptions } from "../types"; import type { BetterAuthOptions } from "../types";
import { getMigrations } from "../cli/utils/get-migration";
export async function getTestInstance<O extends Partial<BetterAuthOptions>>( export async function getTestInstance<O extends Partial<BetterAuthOptions>>(
options?: O, options?: O,
@@ -31,7 +32,6 @@ export async function getTestInstance<O extends Partial<BetterAuthOptions>>(
database: { database: {
provider: "sqlite", provider: "sqlite",
url: dbName, url: dbName,
autoMigrate: true,
}, },
emailAndPassword: { emailAndPassword: {
enabled: true, enabled: true,
@@ -44,7 +44,7 @@ export async function getTestInstance<O extends Partial<BetterAuthOptions>>(
} as O extends undefined ? typeof opts : O & typeof opts); } as O extends undefined ? typeof opts : O & typeof opts);
async function createTestUser() { async function createTestUser() {
await auth.api.signUpCredential({ await auth.api.signUpEmail({
body: { body: {
email: "test@test.com", email: "test@test.com",
password: "test123456", password: "test123456",
@@ -57,11 +57,16 @@ export async function getTestInstance<O extends Partial<BetterAuthOptions>>(
}; };
} }
beforeAll(async () => {
const { runMigrations } = await getMigrations(opts);
await runMigrations();
});
afterAll(async () => { afterAll(async () => {
await fs.unlink(dbName); await fs.unlink(dbName);
}); });
const client = createAuthClient<typeof auth>({ const client = createAuthClient({
customFetchImpl: async (url, init) => { customFetchImpl: async (url, init) => {
const req = new Request(url.toString(), init); const req = new Request(url.toString(), init);
return auth.handler(req); return auth.handler(req);

View File

@@ -1,9 +1,11 @@
import type { AuthContext } from "../init";
export const shimContext = <T extends Record<string, any>>( export const shimContext = <T extends Record<string, any>>(
organizationObject: T, originalObject: T,
newContext: Record<string, any>, newContext: Record<string, any>,
) => { ) => {
const shimmedObj: Record<string, any> = {}; const shimmedObj: Record<string, any> = {};
for (const [key, value] of Object.entries(organizationObject)) { for (const [key, value] of Object.entries(originalObject)) {
shimmedObj[key] = (ctx: Record<string, any>) => { shimmedObj[key] = (ctx: Record<string, any>) => {
return value({ return value({
...ctx, ...ctx,
@@ -20,3 +22,54 @@ export const shimContext = <T extends Record<string, any>>(
} }
return shimmedObj as T; return shimmedObj as T;
}; };
export const shimEndpoint = (ctx: AuthContext, value: any) => {
return async (context: any) => {
for (const plugin of ctx.options.plugins || []) {
if (plugin.hooks?.before) {
for (const hook of plugin.hooks.before) {
const match = hook.matcher({
...context,
...value,
});
if (match) {
const hookRes = await hook.handler(context);
if (hookRes && "context" in hookRes) {
context = {
...context,
...hookRes.context,
...value,
};
}
}
}
}
}
//@ts-ignore
const endpointRes = value({
...context,
context: {
...ctx,
...context.context,
},
});
let response = endpointRes;
for (const plugin of ctx.options.plugins || []) {
if (plugin.hooks?.after) {
for (const hook of plugin.hooks.after) {
const match = hook.matcher(context);
if (match) {
const obj = Object.assign(context, {
returned: endpointRes,
});
const hookRes = await hook.handler(obj);
if (hookRes && "response" in hookRes) {
response = hookRes.response as any;
}
}
}
}
}
return response;
};
};

305
pnpm-lock.yaml generated
View File

@@ -379,6 +379,18 @@ importers:
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tabler/icons-react':
specifier: ^3.12.0
version: 3.12.0(react@18.3.1)
'@tsparticles/engine':
specifier: ^3.5.0
version: 3.5.0
'@tsparticles/react':
specifier: ^3.0.0
version: 3.0.0(@tsparticles/engine@3.5.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tsparticles/slim':
specifier: ^3.5.0
version: 3.5.0
better-auth: better-auth:
specifier: workspace:0.0.2-beta.8 specifier: workspace:0.0.2-beta.8
version: link:../packages/better-auth version: link:../packages/better-auth
@@ -391,6 +403,9 @@ importers:
cmdk: cmdk:
specifier: 1.0.0 specifier: 1.0.0
version: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
cobe:
specifier: ^0.6.3
version: 0.6.3
date-fns: date-fns:
specifier: ^3.6.0 specifier: ^3.6.0
version: 3.6.0 version: 3.6.0
@@ -424,6 +439,9 @@ importers:
lucide-react: lucide-react:
specifier: ^0.435.0 specifier: ^0.435.0
version: 0.435.0(react@18.3.1) version: 0.435.0(react@18.3.1)
mini-svg-data-uri:
specifier: ^1.4.4
version: 1.4.4
next: next:
specifier: ^14.2.5 specifier: ^14.2.5
version: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 14.2.5(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -458,7 +476,7 @@ importers:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
tailwind-merge: tailwind-merge:
specifier: ^2.5.0 specifier: ^2.5.2
version: 2.5.2 version: 2.5.2
tailwindcss-animate: tailwindcss-animate:
specifier: ^1.0.7 specifier: ^1.0.7
@@ -3087,6 +3105,112 @@ packages:
'@ts-morph/common@0.24.0': '@ts-morph/common@0.24.0':
resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==} resolution: {integrity: sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==}
'@tsparticles/basic@3.5.0':
resolution: {integrity: sha512-oty33TxM2aHWrzcwWRic1bQ04KBCdpnvzv8JXEkx5Uyp70vgVegUbtKmwGki3shqKZIt3v2qE4I8NsK6onhLrA==}
'@tsparticles/engine@3.5.0':
resolution: {integrity: sha512-RCwrJ2SvSYdhXJ24oUCjSUKEZQ9lXwObOWMvfMC9vS6/bk+Qo0N7Xx8AfumqzP/LebB1YJdlCvuoJMauAon0Pg==}
'@tsparticles/interaction-external-attract@3.5.0':
resolution: {integrity: sha512-BQYjoHtq7yaETSvPtzKt93OO9MZ1WuDZj7cFPG+iujNekXiwhLRQ89a+QMcsTrCLx70KLJ7SuTzQL5MT1/kb2Q==}
'@tsparticles/interaction-external-bounce@3.5.0':
resolution: {integrity: sha512-H/0//dn4zwKes8zWIjolfeokL0VAlj+EkK7LUhznPhPu+Gt+h6aJqPlwC2MdI5Rvcnps8dT7YoCBWBQ4tJH6zg==}
'@tsparticles/interaction-external-bubble@3.5.0':
resolution: {integrity: sha512-xTS4PQDMC5j9qOAFTC1M9DfBTJl8P8M41ySGtZHnCvVqG0oLlLSw15msniamjXyaoA4tZvBPM6G+GmFdgE9w1A==}
'@tsparticles/interaction-external-connect@3.5.0':
resolution: {integrity: sha512-VSpyZ0P8Hu4nq6C917X3tnwEROfGjrm0ivWJmbBv/lFJ9euZ2VeezeITNZNtNvt/hS5vLI8npDetB/wtd994HQ==}
'@tsparticles/interaction-external-grab@3.5.0':
resolution: {integrity: sha512-WOTWSGVerlfJZ9zwq8Eyutq1h0LAr1hI/Fs8j7s5qabZjxPzUBV8rhgghZ/nGrHEiB6j8SW4XMHkN6XR0VM9Ww==}
'@tsparticles/interaction-external-pause@3.5.0':
resolution: {integrity: sha512-Hnj1mBH5X3d3zwTP6R+tYn45uTq5XGLDINhEzl30EAjXK30LQe8/RgE91O4CsMSrlTmouG0OuHYGC3nyrn/dcw==}
'@tsparticles/interaction-external-push@3.5.0':
resolution: {integrity: sha512-8UNt5lYRhydDJCK7AznR3s1nJj3OCeLcDknARoq7hATdI+G151QAubD9NUUURCZ1GdXpftT5Bh0Bl1YtiZwOhg==}
'@tsparticles/interaction-external-remove@3.5.0':
resolution: {integrity: sha512-+qiVRnR8xywg++gn8fagwpuQVh0WWKxIMkY6l6lMw9UoXz8L6MUVgvWaT632EVmkrTgM43pZ1m0W3m9aBY9rZw==}
'@tsparticles/interaction-external-repulse@3.5.0':
resolution: {integrity: sha512-lTF7iLqCCQ3AbQSDVTpE3TihoVvI322/5QTqQmwylrrmjbDxGu4Hym4BHK5GqDHOdodOnwU2DWjRF5cRx3aPPg==}
'@tsparticles/interaction-external-slow@3.5.0':
resolution: {integrity: sha512-KYp1GWbXdnLunFvHJt2YMZMMITebAt0XkzisKoSe+rfvoCbcMGXI2XvDYb0UkGvd8sKTSnHcn7cGH8dhVXXYaQ==}
'@tsparticles/interaction-particles-attract@3.5.0':
resolution: {integrity: sha512-ICnT9+9ZxINh5ZijyxjFXOOMC/jNQgOXPe+5MxgK/WYXE8mRbRzsOdaxiS3zK5PSFlqtztn189anDbfqcHDutQ==}
'@tsparticles/interaction-particles-collisions@3.5.0':
resolution: {integrity: sha512-KrfyXy4l6nW2z0An2FE4z5R4rEiIONYPcDpkBhWqQK+pyLkHhtGYmqmP7Pb22IC9noFzjOCaR5CNVjWP7B+1vA==}
'@tsparticles/interaction-particles-links@3.5.0':
resolution: {integrity: sha512-ZdIixcGcRJMxCq4zxeRAzzbbuN5vVoy3pDDLaO3mnWnfJWywkYZToV0XvOUaHUT2AkUuKa9ZuQKx0LO3z1AO+w==}
'@tsparticles/move-base@3.5.0':
resolution: {integrity: sha512-9oDk7zTxyhUCstj3lHTNTiWAgqIBzWa2o1tVQFK63Qwq+/WxzJCSwZOocC9PAHGM1IP6nA4zYJSfjbMBTrUocA==}
'@tsparticles/move-parallax@3.5.0':
resolution: {integrity: sha512-1NC2OGsbdLc5T4uiRqq7i24b9FIhfaLnx4wVtOQjX48jWfy/ZKOdIEYLFKOPHnaMI0MjinJTNXLi9i6zVNCobg==}
'@tsparticles/plugin-easing-quad@3.5.0':
resolution: {integrity: sha512-Pd44hTiVlaaeQZwRlP+ih8jKmWuIQdkpPUJS0Qwzeck2nfK01IAflDJoxXxGF53vA8QOrz/K6VdVQJShD8yCsg==}
'@tsparticles/react@3.0.0':
resolution: {integrity: sha512-hjGEtTT1cwv6BcjL+GcVgH++KYs52bIuQGW3PWv7z3tMa8g0bd6RI/vWSLj7p//NZ3uTjEIeilYIUPBh7Jfq/Q==}
peerDependencies:
'@tsparticles/engine': ^3.0.2
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@tsparticles/shape-circle@3.5.0':
resolution: {integrity: sha512-59TmXkeeI6Jzv5vt/D3TkclglabaoEXQi2kbDjSCBK68SXRHzlQu29mSAL41Y5S0Ft5ZJKkAQHX1IqEnm8Hyjg==}
'@tsparticles/shape-emoji@3.5.0':
resolution: {integrity: sha512-cxWHxQxnG5vLDltkoxdo7KS87uKPwQgf4SDWy/WCxW4Psm1TEeeSGYMJPVed+wWPspOKmLb7u8OaEexgE2pHHQ==}
'@tsparticles/shape-image@3.5.0':
resolution: {integrity: sha512-lWYg7DTv74dSOnXy+4dr7t1/OSuUmxDpIo12Lbxgx/QBN7A5I/HoqbKcs13TSA0RS1hcuMgtti07BcDTEYW3Dw==}
'@tsparticles/shape-line@3.5.0':
resolution: {integrity: sha512-Qc4jwFEi/VnwmGwQBO/kCJEfNYdKHpeXfrdcqmm9c1B4iYHHDoaXJp6asMTggEfeAWu7fyPaO/7MURiPEqg7Hg==}
'@tsparticles/shape-polygon@3.5.0':
resolution: {integrity: sha512-sqYL+YXpnq3nSWcOEGZaJ4Z7Cb7x8M0iORSLpPdNEIvwDKdPczYyQM95D8ep19Pv1CV5L0uRthV36wg7UpnJ9Q==}
'@tsparticles/shape-square@3.5.0':
resolution: {integrity: sha512-rPHpA4Pzm1W5DIIow+lQS+VS7D2thSBQQbV9eHxb933Wh0/QC3me3w4vovuq7hdtVANhsUVO04n44Gc/2TgHkw==}
'@tsparticles/shape-star@3.5.0':
resolution: {integrity: sha512-EDEJc4MYv3UbOeA3wrZjuJVtZ08PdCzzBij3T/7Tp3HUCf/p9XnfHBd/CPR5Mo6X0xpGfrein8UQN9CjGLHwUA==}
'@tsparticles/slim@3.5.0':
resolution: {integrity: sha512-CKx3VtzsY0fs/dQc41PYtL3edm1z2sBROTgvz3adwqMyTWkQGnjLQhsM777Ebb6Yjf5Jxu4TzWOBc2HO7Cstkg==}
'@tsparticles/updater-color@3.5.0':
resolution: {integrity: sha512-TGGgiLixIg37sst2Fj9IV4XbdMwkT6PYanM7qEqyfmv4hJ/RHMQlCznEe6b7OhChQVBg5ov5EMl/BT4/fIWEYw==}
'@tsparticles/updater-life@3.5.0':
resolution: {integrity: sha512-jlMEq16dwN+rZmW/UmLdqaCe4W0NFrVdmXkZV8QWYgu06a+Ucslz337nHYaP89/9rZWpNua/uq1JDjDzaVD5Jg==}
'@tsparticles/updater-opacity@3.5.0':
resolution: {integrity: sha512-T2YfqdIFV/f5VOg1JQsXu6/owdi9g9K2wrJlBfgteo+IboVp6Lcuo4PGAfilWVkWrTdp1Nz4mz39NrLHfOce2g==}
'@tsparticles/updater-out-modes@3.5.0':
resolution: {integrity: sha512-y6NZe2OSk5SrYdaLwUIQnHICsNEDIdPPJHQ2nAWSvAuPJphlSKjUknc7OaGiFwle6l0OkhWoZZe1rV1ktbw/lA==}
'@tsparticles/updater-rotate@3.5.0':
resolution: {integrity: sha512-j4qPHQd1eUmDoGnIJOsVswHLqtTof1je+b2GTOLB3WIoKmlyUpzQYjVc7PNfLMuCEUubwpZCfcd/vC80VZeWkg==}
'@tsparticles/updater-size@3.5.0':
resolution: {integrity: sha512-TnWlOChBsVZffT2uO0S4ALGSzxT6UAMIVlhGMGFgSeIlktKMqM+dxDGAPrYa1n8IS2dkVGisiXzsV0Ss6Ceu1A==}
'@tsparticles/updater-stroke-color@3.5.0':
resolution: {integrity: sha512-29X1zER+W9IBDv0nTD/jRXnu5rEU7uv7+W1N0B6leBipjAY24sg7Kub2SvXAaBKz6kHHWuCeccBOwIiiTpDqMA==}
'@tybys/wasm-util@0.8.3': '@tybys/wasm-util@0.8.3':
resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==} resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==}
@@ -3854,6 +3978,9 @@ packages:
react: ^18.0.0 react: ^18.0.0
react-dom: ^18.0.0 react-dom: ^18.0.0
cobe@0.6.3:
resolution: {integrity: sha512-WHr7X4o1ym94GZ96h7b1pNemZJacbOzd02dZtnVwuC4oWBaLg96PBmp2rIS1SAhUDhhC/QyS9WEqkpZIs/ZBTg==}
code-block-writer@13.0.2: code-block-writer@13.0.2:
resolution: {integrity: sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==} resolution: {integrity: sha512-XfXzAGiStXSmCIwrkdfvc7FS5Dtj8yelCtyOf2p2skCAfvLd6zu0rGzuS9NSCO3bq1JKpFZ7tbKdKlcd5occQA==}
@@ -6170,6 +6297,9 @@ packages:
pgpass@1.0.5: pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
phenomenon@1.6.0:
resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==}
picocolors@1.0.1: picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@@ -10407,6 +10537,173 @@ snapshots:
mkdirp: 3.0.1 mkdirp: 3.0.1
path-browserify: 1.0.1 path-browserify: 1.0.1
'@tsparticles/basic@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/move-base': 3.5.0
'@tsparticles/shape-circle': 3.5.0
'@tsparticles/updater-color': 3.5.0
'@tsparticles/updater-opacity': 3.5.0
'@tsparticles/updater-out-modes': 3.5.0
'@tsparticles/updater-size': 3.5.0
'@tsparticles/engine@3.5.0': {}
'@tsparticles/interaction-external-attract@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-bounce@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-bubble@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-connect@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-grab@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-pause@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-push@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-remove@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-repulse@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-slow@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-particles-attract@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-particles-collisions@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-particles-links@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/move-base@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/move-parallax@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/plugin-easing-quad@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/react@3.0.0(@tsparticles/engine@3.5.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@tsparticles/engine': 3.5.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@tsparticles/shape-circle@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-emoji@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-image@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-line@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-polygon@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-square@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/shape-star@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/slim@3.5.0':
dependencies:
'@tsparticles/basic': 3.5.0
'@tsparticles/engine': 3.5.0
'@tsparticles/interaction-external-attract': 3.5.0
'@tsparticles/interaction-external-bounce': 3.5.0
'@tsparticles/interaction-external-bubble': 3.5.0
'@tsparticles/interaction-external-connect': 3.5.0
'@tsparticles/interaction-external-grab': 3.5.0
'@tsparticles/interaction-external-pause': 3.5.0
'@tsparticles/interaction-external-push': 3.5.0
'@tsparticles/interaction-external-remove': 3.5.0
'@tsparticles/interaction-external-repulse': 3.5.0
'@tsparticles/interaction-external-slow': 3.5.0
'@tsparticles/interaction-particles-attract': 3.5.0
'@tsparticles/interaction-particles-collisions': 3.5.0
'@tsparticles/interaction-particles-links': 3.5.0
'@tsparticles/move-parallax': 3.5.0
'@tsparticles/plugin-easing-quad': 3.5.0
'@tsparticles/shape-emoji': 3.5.0
'@tsparticles/shape-image': 3.5.0
'@tsparticles/shape-line': 3.5.0
'@tsparticles/shape-polygon': 3.5.0
'@tsparticles/shape-square': 3.5.0
'@tsparticles/shape-star': 3.5.0
'@tsparticles/updater-life': 3.5.0
'@tsparticles/updater-rotate': 3.5.0
'@tsparticles/updater-stroke-color': 3.5.0
'@tsparticles/updater-color@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-life@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-opacity@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-out-modes@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-rotate@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-size@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tsparticles/updater-stroke-color@3.5.0':
dependencies:
'@tsparticles/engine': 3.5.0
'@tybys/wasm-util@0.8.3': '@tybys/wasm-util@0.8.3':
dependencies: dependencies:
tslib: 2.6.3 tslib: 2.6.3
@@ -11393,6 +11690,10 @@ snapshots:
- '@types/react' - '@types/react'
- '@types/react-dom' - '@types/react-dom'
cobe@0.6.3:
dependencies:
phenomenon: 1.6.0
code-block-writer@13.0.2: {} code-block-writer@13.0.2: {}
code-red@1.0.4: code-red@1.0.4:
@@ -14451,6 +14752,8 @@ snapshots:
dependencies: dependencies:
split2: 4.2.0 split2: 4.2.0
phenomenon@1.6.0: {}
picocolors@1.0.1: {} picocolors@1.0.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}