fix: a bunch of fixes

This commit is contained in:
Bereket Engida
2024-09-16 22:06:27 +03:00
parent c945d1ba95
commit 044ba402cc
29 changed files with 1159 additions and 281 deletions

View File

@@ -0,0 +1,129 @@
"use client";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { client } from "@/lib/auth-client";
import { AlertCircle, ArrowLeft, CheckCircle2 } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
export default function Component() {
const [email, setEmail] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
setError("");
// Simulate API call
try {
const res = await client.forgetPassword({
email,
});
// If the API call is successful, set isSubmitted to true
setIsSubmitted(true);
} catch (err) {
setError("An error occurred. Please try again.");
} finally {
setIsSubmitting(false);
}
};
if (isSubmitted) {
return (
<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Check your email</CardTitle>
<CardDescription>
We've sent a password reset link to your email.
</CardDescription>
</CardHeader>
<CardContent>
<Alert>
<CheckCircle2 className="h-4 w-4" />
<AlertDescription>
If you don't see the email, check your spam folder.
</AlertDescription>
</Alert>
</CardContent>
<CardFooter>
<Button
variant="outline"
className="w-full"
onClick={() => setIsSubmitted(false)}
>
<ArrowLeft className="mr-2 h-4 w-4" /> Back to reset password
</Button>
</CardFooter>
</Card>
</main>
);
}
return (
<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
{/* Radial gradient for the container to give a faded look */}
<div className="absolute pointer-events-none inset-0 flex items-center justify-center dark:bg-black bg-white [mask-image:radial-gradient(ellipse_at_center,transparent_20%,black)]"></div>
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Forgot password</CardTitle>
<CardDescription>
Enter your email to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
</div>
{error && (
<Alert variant="destructive" className="mt-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
className="w-full mt-4"
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? "Sending..." : "Send reset link"}
</Button>
</form>
</CardContent>
<CardFooter className="flex justify-center">
<Link href="/sign-in">
<Button variant="link" className="px-0">
Back to sign in
</Button>
</Link>
</CardFooter>
</Card>
</main>
);
}

View File

@@ -0,0 +1,96 @@
"use client";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { client } from "@/lib/auth-client";
import { AlertCircle } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "sonner";
export default function ResetPassword({
params,
}: { params: { token: string } }) {
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState("");
const token = params.token;
const router = useRouter();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setIsSubmitting(true);
setError("");
const res = await client.resetPassword({
token,
newPassword: password,
});
if (res.error) {
toast.error(res.error.message);
}
setIsSubmitting(false);
router.push("/sign-in");
}
return (
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Reset password</CardTitle>
<CardDescription>
Enter new password and confirm it to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit}>
<div className="grid w-full items-center gap-2">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="email">New password</Label>
<PasswordInput
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
autoComplete="password"
placeholder="Password"
/>
</div>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="email">Confirm password</Label>
<PasswordInput
id="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
autoComplete="password"
placeholder="Password"
/>
</div>
</div>
{error && (
<Alert variant="destructive" className="mt-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
<Button
className="w-full mt-4"
type="submit"
disabled={isSubmitting}
>
{isSubmitting ? "Resetting..." : "Reset password"}
</Button>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,120 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { client } from "@/lib/auth-client";
import { AlertCircle, CheckCircle2, Mail } from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
export default function Component() {
const [otp, setOtp] = useState("");
const [isOtpSent, setIsOtpSent] = useState(false);
const [message, setMessage] = useState("");
const [isError, setIsError] = useState(false);
const [isValidated, setIsValidated] = useState(false);
const [OTP, setOTP] = useState("");
// In a real app, this email would come from your authentication context
const userEmail = "user@example.com";
const requestOTP = async () => {
const res = await client.twoFactor.sendOtp({
options: {
body: {
returnOTP: true,
},
},
});
setOTP(res.data?.OTP || "");
// In a real app, this would call your backend API to send the OTP
setMessage("OTP sent to your email");
setIsError(false);
setIsOtpSent(true);
};
const router = useRouter();
const validateOTP = async () => {
const res = await client.twoFactor.verifyOtp({
code: otp,
});
if (res.data) {
setMessage("OTP validated successfully");
setIsError(false);
setIsValidated(true);
router.push("/");
} else {
setIsError(true);
setMessage("Invalid OTP");
}
};
return (
<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Two-Factor Authentication</CardTitle>
<CardDescription>
Verify your identity with a one-time password
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
{!isOtpSent ? (
<Button onClick={requestOTP} className="w-full">
<Mail className="mr-2 h-4 w-4" /> Send OTP to Email
</Button>
) : (
<>
<div className="flex flex-col space-y-1.5">
<Label htmlFor="otp">One-Time Password</Label>
<Label className="py-2">
Use{" "}
<span className="text-blue-100 bg-slate-800 px-2">
{OTP}
</span>{" "}
(on real app, this would be sent to your email)
</Label>
<Input
id="otp"
placeholder="Enter 6-digit OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
maxLength={6}
/>
</div>
<Button
onClick={validateOTP}
disabled={otp.length !== 6 || isValidated}
>
Validate OTP
</Button>
</>
)}
</div>
{message && (
<div
className={`flex items-center gap-2 mt-4 ${isError ? "text-red-500" : "text-primary"
}`}
>
{isError ? (
<AlertCircle className="h-4 w-4" />
) : (
<CheckCircle2 className="h-4 w-4" />
)}
<p className="text-sm">{message}</p>
</div>
)}
</CardContent>
</Card>
</main>
);
}

View File

@@ -0,0 +1,97 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { client } from "@/lib/auth-client";
import { AlertCircle, CheckCircle2 } from "lucide-react";
import Link from "next/link";
import { useState } from "react";
export default function Component() {
const [totpCode, setTotpCode] = useState("");
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (totpCode.length !== 6 || !/^\d+$/.test(totpCode)) {
setError("TOTP code must be 6 digits");
return;
}
client.twoFactor
.verifyTotp({
code: totpCode,
})
.then((res) => {
if (res.data?.status) {
setSuccess(true);
setError("");
} else {
setError("Invalid TOTP code");
}
});
};
return (
<main className="flex flex-col items-center justify-center min-h-[calc(100vh-10rem)]">
<Card className="w-[350px]">
<CardHeader>
<CardTitle>TOTP Verification</CardTitle>
<CardDescription>
Enter your 6-digit TOTP code to authenticate
</CardDescription>
</CardHeader>
<CardContent>
{!success ? (
<form onSubmit={handleSubmit}>
<div className="space-y-2">
<Label htmlFor="totp">TOTP Code</Label>
<Input
id="totp"
type="text"
inputMode="numeric"
pattern="\d{6}"
maxLength={6}
value={totpCode}
onChange={(e) => setTotpCode(e.target.value)}
placeholder="Enter 6-digit code"
required
/>
</div>
{error && (
<div className="flex items-center mt-2 text-red-500">
<AlertCircle className="w-4 h-4 mr-2" />
<span className="text-sm">{error}</span>
</div>
)}
<Button type="submit" className="w-full mt-4">
Verify
</Button>
</form>
) : (
<div className="flex flex-col items-center justify-center space-y-2">
<CheckCircle2 className="w-12 h-12 text-green-500" />
<p className="text-lg font-semibold">Verification Successful</p>
</div>
)}
</CardContent>
<CardFooter className="text-sm text-muted-foreground gap-2">
<Link href="/two-factor/otp">
<Button variant="link" size="sm">
Switch to Email Verification
</Button>
</Link>
</CardFooter>
</Card>
</main>
);
}

View File

@@ -37,7 +37,7 @@ export default function InvitationPage({
setError(res.error.message || "An error occurred")
} else {
setInvitationStatus("accepted");
router.push(`/organizations/${invitation?.organizationSlug}`)
router.push(`/dashboard`)
}
})
};

View File

@@ -145,7 +145,9 @@ export function OrganizationCard(props: {
memberIdOrEmail: member.id
})
}}>
Remove
{
currentMember?.id === member.id ? "Leave" : "Remove"
}
</Button>
)}
</div>

View File

@@ -20,7 +20,7 @@ export default async function DashboardPage() {
return (
<div className="w-full">
<div className="flex gap-4 flex-col">
<UserCard session={JSON.parse(JSON.stringify(session))} activeSessions={activeSessions} />
<UserCard session={JSON.parse(JSON.stringify(session))} activeSessions={JSON.parse(JSON.stringify(activeSessions))} />
<OrganizationCard session={JSON.parse(JSON.stringify(session))} />
</div>
</div>

View File

@@ -17,7 +17,7 @@ import { PasswordInput } from "@/components/ui/password-input";
import { client, signOut, user, useSession } from "@/lib/auth-client";
import { Session } from "@/lib/auth-types";
import { MobileIcon } from "@radix-ui/react-icons";
import { Edit, Fingerprint, Laptop, Loader2, LogOut, Plus, Trash, X } from "lucide-react";
import { Edit, Fingerprint, Laptop, Loader2, LogOut, Plus, QrCode, ShieldCheck, ShieldOff, Trash, X } from "lucide-react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
@@ -25,6 +25,8 @@ import { toast } from "sonner";
import { UAParser } from "ua-parser-js";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import QRCode from "react-qr-code";
import CopyButton from "@/components/ui/copy-button";
export default function UserCard(props: {
@@ -41,6 +43,24 @@ export default function UserCard(props: {
setUa(new UAParser(session?.session.userAgent))
}, [session?.session.userAgent])
const [isTerminating, setIsTerminating] = useState<string>();
const { data: qr } = useQuery({
queryKey: ["two-factor-qr"],
queryFn: async () => {
const res = await client.twoFactor.getTotpUri()
if (res.error) {
throw res.error
}
return res.data
},
enabled: !!session?.user.twoFactorSecret
})
const [isPendingTwoFa, setIsPendingTwoFa] = useState<boolean>(false);
const [twoFaPassword, setTwoFaPassword] = useState<string>("");
const [twoFactorDialog, setTwoFactorDialog] = useState<boolean>(false);
return (
<Card>
<CardHeader>
@@ -71,11 +91,30 @@ export default function UserCard(props: {
{
props.activeSessions.filter((session) => session.userAgent).map((session) => {
return (
<div className="flex items-center gap-2 text-sm text-black font-medium dark:text-white" key={session.id}>
{
new UAParser(session.userAgent).getDevice().type === "mobile" ? <MobileIcon /> : <Laptop size={16} />
}
{new UAParser(session.userAgent).getOS().name}, {new UAParser(session.userAgent).getBrowser().name}
<div key={session.id}>
<div className="flex items-center gap-2 text-sm text-black font-medium dark:text-white">
{
new UAParser(session.userAgent).getDevice().type === "mobile" ? <MobileIcon /> : <Laptop size={16} />
}
{new UAParser(session.userAgent).getOS().name}, {new UAParser(session.userAgent).getBrowser().name}
<button className="text-red-500 opacity-80 cursor-pointer text-xs border-muted-foreground border-red-600 underline " onClick={async () => {
setIsTerminating(session.id)
const res = await client.user.revokeSession({
id: session.id,
})
if (res.error) {
toast.error(res.error.message)
} else {
toast.success("Session terminated successfully")
}
router.refresh()
setIsTerminating(undefined)
}}>
{
isTerminating === session.id ? <Loader2 size={15} className="animate-spin" /> : session.id === props.session?.session.id ? "Sign Out" : "Terminate"
}
</button>
</div>
</div>
)
})
@@ -93,35 +132,128 @@ export default function UserCard(props: {
</div>
<div className="flex flex-col gap-2">
<p className="text-sm">
2FA Authentication
Two Factor
</p>
{session?.user.twoFactorEnabled ? (
<Button
variant="secondary"
className="gap-2"
onClick={async () => {
const res = await client.twoFactor.disable();
if (res.error) {
toast.error(res.error.message);
}
}}
>
Disable 2FA
</Button>
) : (
<Button
variant="outline"
className="gap-2"
onClick={async () => {
const res = await client.twoFactor.enable();
if (res.error) {
toast.error(res.error.message);
}
}}
>
<p>Enable 2FA</p>
</Button>
)}
<div className="flex gap-2">
{
session?.user.twoFactorEnabled && (
<Dialog >
<DialogTrigger asChild>
<Button variant="outline" className="gap-2">
<QrCode size={16} />
<span className="text-sm">
Scan QR Code
</span>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
Scan QR Code
</DialogTitle>
<DialogDescription>
Scan the QR code with your TOTP app
</DialogDescription>
</DialogHeader>
<div className="flex items-center justify-center">
<QRCode value={qr?.totpURI || ""} />
</div>
<div className="flex gap-2 items-center justify-center">
<p className="text-sm text-muted-foreground">
Copy URI to clipboard
</p>
<CopyButton textToCopy={qr?.totpURI || ""} />
</div>
</DialogContent>
</Dialog>
)
}
<Dialog open={twoFactorDialog} onOpenChange={setTwoFactorDialog}>
<DialogTrigger asChild>
<Button
variant={
session?.user.twoFactorEnabled ? "destructive" : "outline"
}
className="gap-2"
>
{
session?.user.twoFactorEnabled ? <ShieldOff size={16} /> : <ShieldCheck size={16} />
}
<span className="text-sm">
{
session?.user.twoFactorEnabled ? "Disable 2FA" : "Enable 2FA"
}
</span>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{
session?.user.twoFactorEnabled ? "Disable 2FA" : "Enable 2FA"
}
</DialogTitle>
<DialogDescription>
{
session?.user.twoFactorEnabled ? "Disable the second factor authentication from your account" : "Enable 2FA to secure your account"
}
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-2">
<Label htmlFor="password">Password</Label>
<PasswordInput id="password" placeholder="Password" value={twoFaPassword} onChange={(e) => setTwoFaPassword(e.target.value)} />
</div>
<DialogFooter>
<Button
disabled={isPendingTwoFa}
onClick={async () => {
if (twoFaPassword.length < 8) {
toast.error("Password must be at least 8 characters");
return;
}
setIsPendingTwoFa(true);
if (session?.user.twoFactorEnabled) {
const res = await client.twoFactor.disable({
//@ts-ignore
password: twoFaPassword,
options: {
onError(context) {
toast.error(context.error.message);
},
onSuccess() {
toast("2FA disabled successfully");
setTwoFactorDialog(false);
}
}
})
} else {
const res = await client.twoFactor.enable({
password: twoFaPassword,
options: {
onError(context) {
toast.error(context.error.message);
},
onSuccess() {
toast.success("2FA enabled successfully");
setTwoFactorDialog(false);
}
}
})
}
setIsPendingTwoFa(false);
setTwoFaPassword("");
}}>
{
isPendingTwoFa ? <Loader2 size={15} className="animate-spin" /> : session?.user.twoFactorEnabled ? "Disable 2FA" : "Enable 2FA"
}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</div>
</div>
</CardContent>

View File

@@ -38,7 +38,7 @@ export default async function Home() {
<Link href="/sign-in">
<Button className="gap-2">
<svg xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 24 24"><path fill="currentColor" d="M5 3H3v4h2V5h14v14H5v-2H3v4h18V3zm12 8h-2V9h-2V7h-2v2h2v2H3v2h10v2h-2v2h2v-2h2v-2h2z"></path></svg>
Sign In
Get Started
</Button>
</Link>
)

View File

@@ -77,25 +77,27 @@ export default function SignIn() {
<Label>Remember me</Label>
</div>
<Button type="submit" className="w-full" onClick={async () => {
await signIn.email({
email: email,
password: password,
callbackURL: "/dashboard",
dontRememberMe: !rememberMe,
options: {
onRequest: () => {
setLoading(true)
},
onResponse: () => {
setLoading(false)
},
onError: (ctx) => {
toast.error(ctx.error.message)
<Button type="submit" className="w-full"
disabled={loading}
onClick={async () => {
await signIn.email({
email: email,
password: password,
callbackURL: "/dashboard",
dontRememberMe: !rememberMe,
options: {
onRequest: () => {
setLoading(true)
},
onResponse: () => {
setLoading(false)
},
onError: (ctx) => {
toast.error(ctx.error.message)
}
}
}
})
}}>
})
}}>
{
loading ? <Loader2 size={16} className="animate-spin" /> : "Login"
}
@@ -103,7 +105,12 @@ export default function SignIn() {
<Button
variant="outline"
className="w-full gap-2"
onClick={async () => { }}
onClick={async () => {
await signIn.social({
provider: "github",
callbackURL: "/dashboard",
})
}}
>
<GitHubLogoIcon />
Continue with Github
@@ -112,7 +119,10 @@ export default function SignIn() {
variant="outline"
className="w-full gap-2"
onClick={async () => {
await signIn.social({
provider: "google",
callbackURL: "/dashboard",
})
}}
>
<svg
@@ -140,7 +150,11 @@ export default function SignIn() {
</svg>
Continue with Google
</Button>
<Button variant="outline" className="gap-2" onClick={async () => { }}>
<Button variant="outline" className="gap-2" onClick={async () => {
await signIn.passkey({
callbackURL: "/dashboard",
})
}}>
<Key size={16} />
Sign-in with Passkey
</Button>

View File

@@ -14,7 +14,7 @@ import { Label } from "@/components/ui/label";
import { PasswordInput } from "@/components/ui/password-input";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import { useState } from "react";
import { signUp } from "@/lib/auth-client";
import { client, signIn, signUp } from "@/lib/auth-client";
import Image from "next/image";
import { Loader2, X } from "lucide-react";
import { toast } from "sonner";
@@ -139,26 +139,28 @@ export function SignUp() {
</div>
</div>
</div>
<Button type="submit" className="w-full" onClick={async () => {
await signUp.email({
email,
password,
name: `${firstName} ${lastName}`,
image: image ? await convertImageToBase64(image) : "",
callbackURL: "/dashboard",
options: {
onResponse: () => {
setLoading(false)
},
onRequest: () => {
setLoading(true)
},
onError: (ctx) => {
toast.error(ctx.error.message)
},
}
})
}}>
<Button type="submit" className="w-full"
disabled={loading}
onClick={async () => {
await signUp.email({
email,
password,
name: `${firstName} ${lastName}`,
image: image ? await convertImageToBase64(image) : "",
callbackURL: "/dashboard",
options: {
onResponse: () => {
setLoading(false)
},
onRequest: () => {
setLoading(true)
},
onError: (ctx) => {
toast.error(ctx.error.message)
},
}
})
}}>
{
loading ? <Loader2 size={16} className="animate-spin" /> : "Create an account"
}
@@ -166,7 +168,21 @@ export function SignUp() {
<Button
variant="outline"
className="w-full gap-2"
onClick={async () => { }}
onClick={async () => {
const res = await client.signIn.social({
provider: "google",
callbackURL: "/dashboard",
options: {
onRequest: () => {
setLoading(true)
},
onResponse: () => {
setLoading(false)
},
}
})
}}
disabled={loading}
>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -196,7 +212,21 @@ export function SignUp() {
<Button
variant="outline"
className="w-full gap-2"
onClick={async () => { }}
onClick={async () => {
await signIn.social({
provider: "github",
callbackURL: "/dashboard",
options: {
onRequest: () => {
setLoading(true)
},
onResponse: () => {
setLoading(false)
},
}
})
}}
disabled={loading}
>
<GitHubLogoIcon />
Continue with Github

View File

@@ -1,11 +1,30 @@
import { createAuthClient } from "better-auth/react";
import { organizationClient, passkeyClient, twoFactorClient } from "better-auth/client/plugins"
import {
organizationClient,
passkeyClient,
twoFactorClient,
} from "better-auth/client/plugins";
export const client = createAuthClient({
plugins: [organizationClient(), twoFactorClient(), passkeyClient()],
fetchOptions: {
credentials: 'include'
}
plugins: [
organizationClient(),
twoFactorClient({
twoFactorPage: "/two-factor",
}),
passkeyClient(),
],
fetchOptions: {
credentials: "include",
},
});
export const { signUp, signIn, signOut, useSession, user, organization, useListOrganizations, useActiveOrganization } = client;
export const {
signUp,
signIn,
signOut,
useSession,
user,
organization,
useListOrganizations,
useActiveOrganization,
} = client;

View File

@@ -2,17 +2,38 @@ import { betterAuth } from "better-auth";
import { organization, passkey, twoFactor } from "better-auth/plugins";
import { Resend } from "resend";
import { reactInvitationEmail } from "./email/invitation";
import { LibsqlDialect } from "@libsql/kysely-libsql";
import { github, google } from "better-auth/social-providers";
import { reactResetPasswordEmail } from "./email/rest-password";
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",
},
database: new LibsqlDialect({
url: process.env.TURSO_DATABASE_URL || "",
authToken: process.env.TURSO_AUTH_TOKEN || "",
}),
emailAndPassword: {
enabled: true,
async sendResetPasswordToken(token, user) {
const res = await resend.emails.send({
from,
to: user.email,
subject: "Reset your password",
react: reactResetPasswordEmail({
username: user.email,
resetLink: `${
process.env.NODE_ENV === "development"
? "http://localhost:3000"
: process.env.NEXT_PUBLIC_APP_URL ||
process.env.VERCEL_URL ||
process.env.BETTER_AUTH_URL
}/reset-password/${token}`,
}),
});
console.log(res, user.email);
},
},
plugins: [
organization({
@@ -39,9 +60,25 @@ export const auth = betterAuth({
console.log(res, data.email);
},
}),
twoFactor(),
twoFactor({
otpOptions: {
sendOTP(user, otp) {
console.log({ otp });
},
},
}),
passkey({
rpID: "localhost",
}),
],
socialProvider: [
github({
clientId: process.env.GITHUB_CLIENT_ID || "",
clientSecret: process.env.GITHUB_CLIENT_SECRET || "",
}),
google({
clientId: process.env.GOOGLE_CLIENT_ID || "",
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
}),
],
});

View File

@@ -0,0 +1,71 @@
import {
Body,
Button,
Container,
Head,
Heading,
Hr,
Html,
Link,
Preview,
Text,
Tailwind,
Section,
} from "@react-email/components";
import * as React from "react";
interface BetterAuthResetPasswordEmailProps {
username?: string;
resetLink?: string;
}
export const ResetPasswordEmail = ({
username,
resetLink,
}: BetterAuthResetPasswordEmailProps) => {
const previewText = `Reset your BetterAuth password`;
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">
Reset your <strong>Better Auth</strong> password
</Heading>
<Text className="text-black text-[14px] leading-[24px]">
Hello {username},
</Text>
<Text className="text-black text-[14px] leading-[24px]">
We received a request to reset your password for your Better Auth account. If you didn't make this request, you can safely ignore this email.
</Text>
<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={resetLink}
>
Reset Password
</Button>
</Section>
<Text className="text-black text-[14px] leading-[24px]">
Or copy and paste this URL into your browser:{" "}
<Link href={resetLink} className="text-blue-600 no-underline">
{resetLink}
</Link>
</Text>
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
<Text className="text-[#666666] text-[12px] leading-[24px]">
If you didn't request a password reset, please ignore this email or contact support if you have concerns.
</Text>
</Container>
</Body>
</Tailwind>
</Html>
);
};
export function reactResetPasswordEmail(props: BetterAuthResetPasswordEmailProps) {
console.log(props);
return <ResetPasswordEmail {...props} />;
}

View File

@@ -1,5 +1,5 @@
{
"name": "@better-auth/demo",
"name": "@better-auth/demo",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -63,6 +63,7 @@
"react-day-picker": "8.10.1",
"react-dom": "19.0.0-rc-7771d3a7-20240827",
"react-hook-form": "^7.53.0",
"react-qr-code": "^2.0.15",
"react-resizable-panels": "^2.1.2",
"recharts": "^2.12.7",
"resend": "^4.0.0",

View File

@@ -39,10 +39,6 @@ export const callbackOAuth = createAuthEndpoint(
);
let tokens: OAuth2Tokens;
try {
console.log({
data: c.query.code,
codeVerifier,
});
tokens = await provider.validateAuthorizationCode(
c.query.code,
codeVerifier,

View File

@@ -148,7 +148,13 @@ export const revokeSession = createAuthEndpoint(
},
async (ctx) => {
const id = ctx.body.id;
await ctx.context.internalAdapter.deleteSession(id);
console.log(id);
try {
await ctx.context.internalAdapter.deleteSession(id);
} catch (error) {
ctx.context.logger.error(error);
return ctx.json(null, { status: 500 });
}
return ctx.json({
status: true,
});
@@ -165,9 +171,14 @@ export const revokeSessions = createAuthEndpoint(
requireHeaders: true,
},
async (ctx) => {
await ctx.context.internalAdapter.deleteSessions(
ctx.context.session.user.id,
);
try {
await ctx.context.internalAdapter.deleteSessions(
ctx.context.session.user.id,
);
} catch (error) {
ctx.context.logger.error(error);
return ctx.json(null, { status: 500 });
}
return ctx.json({
status: true,
});

View File

@@ -51,23 +51,27 @@ export const removeMember = createAuthEndpoint(
},
});
}
if (
(session.user.email === ctx.body.memberIdOrEmail ||
member.id === ctx.body.memberIdOrEmail) &&
member.role === (ctx.context.orgOptions?.creatorRole || "owner")
) {
const isLeaving =
session.user.email === ctx.body.memberIdOrEmail ||
member.id === ctx.body.memberIdOrEmail;
const isOwnerLeaving =
isLeaving &&
member.role === (ctx.context.orgOptions?.creatorRole || "owner");
if (isOwnerLeaving) {
return ctx.json(null, {
status: 400,
body: {
message: "You cannot delete yourself",
message: "You cannot leave the organization as the owner",
},
});
}
const canDeleteMember = role.authorize({
member: ["delete"],
});
if (canDeleteMember.error) {
const canDeleteMember =
isLeaving ||
role.authorize({
member: ["delete"],
}).success;
if (!canDeleteMember) {
return ctx.json(null, {
body: {
message: "You are not allowed to delete this member",

View File

@@ -272,6 +272,19 @@ export const setActiveOrganization = createAuthEndpoint(
}
orgId = sessionOrgId;
}
const isMember = await adapter.findMemberByOrgId({
userId: session.user.id,
organizationId: orgId,
});
if (!isMember) {
await adapter.setActiveOrganization(session.session.id, null);
return ctx.json(null, {
status: 400,
body: {
message: "You are not a member of this organization",
},
});
}
await adapter.setActiveOrganization(session.session.id, orgId);
const organization = await adapter.findFullOrganization(
orgId,

View File

@@ -35,6 +35,7 @@ export const getPasskeyActions = (
method: "POST",
body: {
email: opts?.email,
callbackURL: opts?.callbackURL,
},
},
);

View File

@@ -203,11 +203,12 @@ export const passkey = (options?: PasskeyOptions) => {
*/
const data: WebAuthnCookieType = {
expectedChallenge: options.challenge,
callbackURL: ctx.body?.callbackURL,
userData: {
id: session?.user.id || "",
},
callbackURL: ctx.body?.callbackURL,
};
await ctx.setSignedCookie(
opts.advanced.webAuthnChallengeCookie,
JSON.stringify(data),
@@ -337,6 +338,7 @@ export const passkey = (options?: PasskeyOptions) => {
status: 400,
});
}
console.log({ challengeString });
const { expectedChallenge, callbackURL } = JSON.parse(
challengeString,
) as WebAuthnCookieType;
@@ -400,6 +402,7 @@ export const passkey = (options?: PasskeyOptions) => {
ctx.request,
);
await setSessionCookie(ctx, s.id);
if (callbackURL) {
return ctx.json({
url: callbackURL,

View File

@@ -11,6 +11,7 @@ import { totp2fa } from "./totp";
import type { TwoFactorOptions, UserWithTwoFactor } from "./types";
import type { Session } from "../../adapters/schema";
import { TWO_FACTOR_COOKIE_NAME, TRUST_DEVICE_COOKIE_NAME } from "./constant";
import { validatePassword } from "../../utils/password";
export const twoFactor = (options?: TwoFactorOptions) => {
const totp = totp2fa({
@@ -29,10 +30,29 @@ export const twoFactor = (options?: TwoFactorOptions) => {
"/two-factor/enable",
{
method: "POST",
body: z.object({
password: z.string().min(8),
}),
use: [sessionMiddleware],
},
async (ctx) => {
const user = ctx.context.session.user as UserWithTwoFactor;
const { password } = ctx.body;
const isPasswordValid = await validatePassword(ctx, {
password,
userId: user.id,
});
if (!isPasswordValid) {
return ctx.json(
{ status: false },
{
status: 400,
body: {
message: "Invalid password",
},
},
);
}
const secret = generateRandomString(16, alphabet("a-z", "0-9", "-"));
const encryptedSecret = symmetricEncrypt({
key: ctx.context.secret,
@@ -63,10 +83,29 @@ export const twoFactor = (options?: TwoFactorOptions) => {
"/two-factor/disable",
{
method: "POST",
body: z.object({
password: z.string().min(8),
}),
use: [sessionMiddleware],
},
async (ctx) => {
const user = ctx.context.session.user as UserWithTwoFactor;
const { password } = ctx.body;
const isPasswordValid = await validatePassword(ctx, {
password,
userId: user.id,
});
if (!isPasswordValid) {
return ctx.json(
{ status: false },
{
status: 400,
body: {
message: "Invalid password",
},
},
);
}
await ctx.context.adapter.update({
model: "user",
update: {

View File

@@ -75,6 +75,11 @@ export const totp2fa = (options: TOTPOptions) => {
});
}
const user = ctx.context.session.user as UserWithTwoFactor;
if (!user.twoFactorSecret) {
throw new APIError("BAD_REQUEST", {
message: "totp isn't enabled",
});
}
return {
totpURI: createTOTPKeyURI(
options?.issuer || "BetterAuth",

View File

@@ -38,6 +38,7 @@ describe("two factor", async () => {
}
it("should enable two factor", async () => {
const res = await client.twoFactor.enable({
password: testUser.password,
options: {
headers,
},
@@ -183,6 +184,7 @@ describe("two factor", async () => {
it("should disable two factor", async () => {
const res = await client.twoFactor.disable({
password: testUser.password,
options: {
headers,
},

View File

@@ -55,7 +55,12 @@ export const google = (options: GoogleOptions) => {
throw new BetterAuthError("codeVerifier is required for Google");
}
const _scopes = scopes || ["email", "profile"];
return googleArctic.createAuthorizationURL(state, codeVerifier, _scopes);
const url = googleArctic.createAuthorizationURL(
state,
codeVerifier,
_scopes,
);
return url;
},
validateAuthorizationCode: async (code, codeVerifier, redirectURI) => {
return validateAuthorizationCode({

View File

@@ -1,6 +1,7 @@
import { createOAuth2Request, sendTokenRequest } from "arctic/dist/request";
import { OAuth2Tokens } from "arctic";
import type { ProviderOptions } from ".";
import { getBaseURL } from "../utils/base-url";
import { betterFetch } from "@better-fetch/fetch";
export function getRedirectURI(providerId: string, redirectURI?: string) {
return redirectURI || `${getBaseURL()}/callback/${providerId}`;
@@ -26,7 +27,19 @@ export async function validateAuthorizationCode({
body.set("redirect_uri", redirectURI);
body.set("client_id", options.clientId);
body.set("client_secret", options.clientSecret);
const request = createOAuth2Request(tokenEndpoint, body);
const tokens = await sendTokenRequest(request);
const { data, error } = await betterFetch<object>(tokenEndpoint, {
method: "POST",
body: body,
headers: {
"content-type": "application/x-www-form-urlencoded",
accept: "application/json",
"user-agent": "better-auth",
},
});
console.log({ data, error, body });
if (error) {
throw error;
}
const tokens = new OAuth2Tokens(data);
return tokens;
}

View File

@@ -2,10 +2,7 @@ import type { Primitive } from "zod";
export type LiteralString = "" | (string & Record<never, never>);
export type Prettify<T> = {
[key in keyof T]: T[key];
} & {};
export type Prettify<T> = Omit<T, never>;
export type LiteralUnion<LiteralType, BaseType extends Primitive> =
| LiteralType
| (BaseType & Record<never, never>);
@@ -30,25 +27,3 @@ export type RequiredKeysOf<BaseType extends object> = Exclude<
export type HasRequiredKeys<BaseType extends object> =
RequiredKeysOf<BaseType> extends never ? false : true;
export type WithoutEmpty<T> = T extends T ? ({} extends T ? never : T) : never;
type LastOf<T> = UnionToIntersection<
T extends any ? () => T : never
> extends () => infer R
? R
: never;
type Push<T extends any[], V> = [...T, V];
type TuplifyUnion<
T,
L = LastOf<T>,
N = [T] extends [never] ? true : false,
> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>;
// The magic happens here!
export type Tuple<
T,
A extends T[] = [],
> = TuplifyUnion<T>["length"] extends A["length"]
? [...A]
: Tuple<T, [T, ...A]>;

View File

@@ -0,0 +1,23 @@
import type { GenericEndpointContext } from "../types/context";
export async function validatePassword(
ctx: GenericEndpointContext,
data: {
password: string;
userId: string;
},
) {
const accounts = await ctx.context.internalAdapter.findAccounts(data.userId);
const credentialAccount = accounts?.find(
(account) => account.providerId === "credential",
);
const currentPassword = credentialAccount?.password;
if (!credentialAccount || !currentPassword) {
return false;
}
const compare = await ctx.context.password.verify(
currentPassword,
data.password,
);
return compare;
}

314
pnpm-lock.yaml generated
View File

@@ -198,6 +198,9 @@ importers:
react-hook-form:
specifier: ^7.53.0
version: 7.53.0(react@19.0.0-rc-7771d3a7-20240827)
react-qr-code:
specifier: ^2.0.15
version: 2.0.15(react@19.0.0-rc-7771d3a7-20240827)
react-resizable-panels:
specifier: ^2.1.2
version: 2.1.2(react-dom@19.0.0-rc-7771d3a7-20240827(react@19.0.0-rc-7771d3a7-20240827))(react@19.0.0-rc-7771d3a7-20240827)
@@ -264,7 +267,7 @@ importers:
version: 1.12.2(hono@4.5.9)
better-auth:
specifier: ^0.0.4
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2))
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.6(typescript@5.6.2))
dotenv:
specifier: ^16.4.5
version: 16.4.5
@@ -526,13 +529,13 @@ importers:
dependencies:
better-auth:
specifier: ^0.0.4
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2))
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.6(typescript@5.6.2))
nuxt:
specifier: ^3.13.0
version: 3.13.0(@biomejs/biome@1.7.3)(@parcel/watcher@2.4.1)(@types/node@22.3.0)(eslint@9.9.1(jiti@1.21.6))(ioredis@5.4.1)(lightningcss@1.22.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(terser@5.31.6)(typescript@5.6.2)(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))
vue:
specifier: latest
version: 3.5.5(typescript@5.6.2)
version: 3.5.6(typescript@5.6.2)
dev/solidjs:
dependencies:
@@ -565,7 +568,7 @@ importers:
dependencies:
better-auth:
specifier: ^0.0.4
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.5.4)(vue@3.5.5(typescript@5.5.4))
version: 0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.5.4)(vue@3.5.6(typescript@5.5.4))
devDependencies:
'@sveltejs/adapter-auto':
specifier: ^3.0.0
@@ -581,7 +584,7 @@ importers:
version: 4.2.19
svelte-check:
specifier: ^3.6.0
version: 3.8.6(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.44)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.44)(svelte@4.2.19)
version: 3.8.6(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.47)(svelte@4.2.19)
typescript:
specifier: ^5.0.0
version: 5.5.4
@@ -1124,7 +1127,7 @@ importers:
version: 2.2.5(react@18.3.1)
tsup:
specifier: ^8.2.4
version: 8.2.4(@swc/core@1.7.26(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0)(typescript@5.6.0-beta)(yaml@2.5.0)
version: 8.2.4(@swc/core@1.7.26(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.0)(typescript@5.6.0-beta)(yaml@2.5.0)
typescript:
specifier: 5.6.0-beta
version: 5.6.0-beta
@@ -2745,7 +2748,7 @@ packages:
'@expo/bunyan@4.0.1':
resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==}
engines: {'0': node >=0.10.0}
engines: {node: '>=0.10.0'}
'@expo/cli@0.18.29':
resolution: {integrity: sha512-X810C48Ss+67RdZU39YEO1khNYo1RmjouRV+vVe0QhMoTe8R6OA3t+XYEdwaNbJ5p/DJN7szfHfNmX2glpC7xg==}
@@ -5824,39 +5827,39 @@ packages:
'@vue/compiler-core@3.5.0':
resolution: {integrity: sha512-ja7cpqAOfw4tyFAxgBz70Z42miNDeaqTxExTsnXDLomRpqfyCgyvZvFp482fmsElpfvsoMJUsvzULhvxUTW6Iw==}
'@vue/compiler-core@3.5.4':
resolution: {integrity: sha512-oNwn+BAt3n9dK9uAYvI+XGlutwuTq/wfj4xCBaZCqwwVIGtD7D6ViihEbyYZrDHIHTDE3Q6oL3/hqmAyFEy9DQ==}
'@vue/compiler-core@3.5.5':
resolution: {integrity: sha512-ZrxcY8JMoV+kgDrmRwlDufz0SjDZ7jfoNZiIBluAACMBmgr55o/jTbxnyrccH6VSJXnFaDI4Ik1UFCiq9r8i7w==}
'@vue/compiler-core@3.5.6':
resolution: {integrity: sha512-r+gNu6K4lrvaQLQGmf+1gc41p3FO2OUJyWmNqaIITaJU6YFiV5PtQSFZt8jfztYyARwqhoCayjprC7KMvT3nRA==}
'@vue/compiler-dom@3.5.0':
resolution: {integrity: sha512-xYjUybWZXl+1R/toDy815i4PbeehL2hThiSGkcpmIOCy2HoYyeeC/gAWK/Y/xsoK+GSw198/T5O31bYuQx5uvQ==}
'@vue/compiler-dom@3.5.4':
resolution: {integrity: sha512-yP9RRs4BDLOLfldn6ah+AGCNovGjMbL9uHvhDHf5wan4dAHLnFGOkqtfE7PPe4HTXIqE7l/NILdYw53bo1C8jw==}
'@vue/compiler-dom@3.5.5':
resolution: {integrity: sha512-HSvK5q1gmBbxRse3S0Wt34RcKuOyjDJKDDMuF3i7NC+QkDFrbAqw8NnrEm/z7zFDxWZa4/5eUwsBOMQzm1RHBA==}
'@vue/compiler-dom@3.5.6':
resolution: {integrity: sha512-xRXqxDrIqK8v8sSScpistyYH0qYqxakpsIvqMD2e5sV/PXQ1mTwtXp4k42yHK06KXxKSmitop9e45Ui/3BrTEw==}
'@vue/compiler-sfc@3.5.0':
resolution: {integrity: sha512-B9DgLtrqok2GLuaFjLlSL15ZG3ZDBiitUH1ecex9guh/ZcA5MCdwuVE6nsfQxktuZY/QY0awJ35/ripIviCQTQ==}
'@vue/compiler-sfc@3.5.4':
resolution: {integrity: sha512-P+yiPhL+NYH7m0ZgCq7AQR2q7OIE+mpAEgtkqEeH9oHSdIRvUO+4X6MPvblJIWcoe4YC5a2Gdf/RsoyP8FFiPQ==}
'@vue/compiler-sfc@3.5.5':
resolution: {integrity: sha512-MzBHDxwZhgQPHrwJ5tj92gdTYRCuPDSZr8PY3+JFv8cv2UD5/WayH5yo0kKCkKfrtJhc39jNSMityHrkMSbfnA==}
'@vue/compiler-sfc@3.5.6':
resolution: {integrity: sha512-pjWJ8Kj9TDHlbF5LywjVso+BIxCY5wVOLhkEXRhuCHDxPFIeX1zaFefKs8RYoHvkSMqRWt93a0f2gNJVJixHwg==}
'@vue/compiler-ssr@3.5.0':
resolution: {integrity: sha512-E263QZmA1dqRd7c3u/sWTLRMpQOT0aZ8av/L9SoD/v/BVMZaWFHPUUBswS+bzrfvG2suJF8vSLKx6k6ba5SUdA==}
'@vue/compiler-ssr@3.5.4':
resolution: {integrity: sha512-acESdTXsxPnYr2C4Blv0ggx5zIFMgOzZmYU2UgvIff9POdRGbRNBHRyzHAnizcItvpgerSKQbllUc9USp3V7eg==}
'@vue/compiler-ssr@3.5.5':
resolution: {integrity: sha512-oFasHnpv/upubjJEmqiTKQYb4qS3ziJddf4UVWuFw6ebk/QTrTUc+AUoTJdo39x9g+AOQBzhOU0ICCRuUjvkmw==}
'@vue/compiler-ssr@3.5.6':
resolution: {integrity: sha512-VpWbaZrEOCqnmqjE83xdwegtr5qO/2OPUC6veWgvNqTJ3bYysz6vY3VqMuOijubuUYPRpG3OOKIh9TD0Stxb9A==}
'@vue/devtools-api@6.6.3':
resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==}
@@ -5872,30 +5875,30 @@ packages:
'@vue/reactivity@3.5.0':
resolution: {integrity: sha512-Ew3F5riP3B3ZDGjD3ZKb9uZylTTPSqt8hAf4sGbvbjrjDjrFb3Jm15Tk1/w7WwTE5GbQ2Qhwxx1moc9hr8A/OQ==}
'@vue/reactivity@3.5.5':
resolution: {integrity: sha512-V4tTWElZQhT73PSK3Wnax9R9m4qvMX+LeKHnfylZc6SLh4Jc5/BPakp6e3zEhKWi5AN8TDzRkGnLkp8OqycYng==}
'@vue/reactivity@3.5.6':
resolution: {integrity: sha512-shZ+KtBoHna5GyUxWfoFVBCVd7k56m6lGhk5e+J9AKjheHF6yob5eukssHRI+rzvHBiU1sWs/1ZhNbLExc5oYQ==}
'@vue/runtime-core@3.5.0':
resolution: {integrity: sha512-mQyW0F9FaNRdt8ghkAs+BMG3iQ7LGgWKOpkzUzR5AI5swPNydHGL5hvVTqFaeMzwecF1g0c86H4yFQsSxJhH1w==}
'@vue/runtime-core@3.5.5':
resolution: {integrity: sha512-2/CFaRN17jgsXy4MpigWFBCAMmLkXPb4CjaHrndglwYSra7ajvkH2cat21dscuXaH91G8fXAeg5gCyxWJ+wCRA==}
'@vue/runtime-core@3.5.6':
resolution: {integrity: sha512-FpFULR6+c2lI+m1fIGONLDqPQO34jxV8g6A4wBOgne8eSRHP6PQL27+kWFIx5wNhhjkO7B4rgtsHAmWv7qKvbg==}
'@vue/runtime-dom@3.5.0':
resolution: {integrity: sha512-NQQXjpdXgyYVJ2M56FJ+lSJgZiecgQ2HhxhnQBN95FymXegRNY/N2htI7vOTwpP75pfxhIeYOJ8mE8sW8KAW6A==}
'@vue/runtime-dom@3.5.5':
resolution: {integrity: sha512-0bQGgCuL+4Muz5PsCLgF4Ata9BTdhHi5VjsxtTDyI0Wy4MgoSvBGaA6bDc7W7CGgZOyirf9LNeetMYHQ05pgpw==}
'@vue/runtime-dom@3.5.6':
resolution: {integrity: sha512-SDPseWre45G38ENH2zXRAHL1dw/rr5qp91lS4lt/nHvMr0MhsbCbihGAWLXNB/6VfFOJe2O+RBRkXU+CJF7/sw==}
'@vue/server-renderer@3.5.0':
resolution: {integrity: sha512-HyDIFUg+l7L4PKrEnJlCYWHUOlm6NxZhmSxIefZ5MTYjkIPfDfkwhX7hqxAQHfgIAE1uLMLQZwuNR/ozI0NhZg==}
peerDependencies:
vue: 3.5.0
'@vue/server-renderer@3.5.5':
resolution: {integrity: sha512-XjRamLIq5f47cxgy+hiX7zUIY+4RHdPDVrPvvMDAUTdW5RJWX/S0ji/rCbm3LWTT/9Co9bvQME8ZI15ahL4/Qw==}
'@vue/server-renderer@3.5.6':
resolution: {integrity: sha512-zivnxQnOnwEXVaT9CstJ64rZFXMS5ZkKxCjDQKiMSvUhXRzFLWZVbaBiNF4HGDqGNNsTgmjcCSmU6TB/0OOxLA==}
peerDependencies:
vue: 3.5.5
vue: 3.5.6
'@vue/shared@3.4.38':
resolution: {integrity: sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==}
@@ -5903,12 +5906,12 @@ packages:
'@vue/shared@3.5.0':
resolution: {integrity: sha512-m9IgiteBpCkFaMNwCOBkFksA7z8QiKc30ooRuoXWUFRDu0mGyNPlFHmbncF0/Kra1RlX8QrmBbRaIxVvikaR0Q==}
'@vue/shared@3.5.4':
resolution: {integrity: sha512-L2MCDD8l7yC62Te5UUyPVpmexhL9ipVnYRw9CsWfm/BGRL5FwDX4a25bcJ/OJSD3+Hx+k/a8LDKcG2AFdJV3BA==}
'@vue/shared@3.5.5':
resolution: {integrity: sha512-0KyMXyEgnmFAs6rNUL+6eUHtUCqCaNrVd+AW3MX3LyA0Yry5SA0Km03CDKiOua1x1WWnIr+W9+S0GMFoSDWERQ==}
'@vue/shared@3.5.6':
resolution: {integrity: sha512-eidH0HInnL39z6wAt6SFIwBrvGOpDWsDxlw3rCgo1B+CQ1781WzQUSU3YjxgdkcJo9Q8S6LmXTkvI+cLHGkQfA==}
'@web3-storage/multipart-parser@1.0.0':
resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==}
@@ -9195,6 +9198,7 @@ packages:
libsql@0.3.19:
resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==}
cpu: [x64, arm64, wasm32]
os: [darwin, linux, win32]
lighthouse-logger@1.4.2:
@@ -10616,6 +10620,9 @@ packages:
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
@@ -10904,6 +10911,10 @@ packages:
resolution: {integrity: sha512-Aweb9unOEpQ3ezu4Q00DPvvM2ZTUitJdNKeP/+uQgr1IBIqu574IaZoURId7BKtWMREwzKa9OgzPzezWGPWFQw==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.4.47:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
@@ -11801,6 +11812,10 @@ packages:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
@@ -13061,8 +13076,8 @@ packages:
typescript:
optional: true
vue@3.5.5:
resolution: {integrity: sha512-ybC+xn67K4+df1yVeov4UjBGyVcXM0a1g7JVZr+pWVUX3xF6ntXU0wIjkTkduZBUIpxTlsftJSxz2kwhsT7dgA==}
vue@3.5.6:
resolution: {integrity: sha512-zv+20E2VIYbcJOzJPUWp03NOGFhMmpCKOfSxVTmCYyYFFko48H9tmuQFzYj7tu4qX1AeXlp9DmhIP89/sSxxhw==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
@@ -15576,17 +15591,17 @@ snapshots:
optionalDependencies:
'@vue/devtools-api': 6.6.3
'@nanostores/vue@0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.5(typescript@5.5.4))':
'@nanostores/vue@0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.6(typescript@5.5.4))':
dependencies:
nanostores: 0.11.2
vue: 3.5.5(typescript@5.5.4)
vue: 3.5.6(typescript@5.5.4)
optionalDependencies:
'@vue/devtools-api': 6.6.3
'@nanostores/vue@0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.5(typescript@5.6.2))':
'@nanostores/vue@0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.6(typescript@5.6.2))':
dependencies:
nanostores: 0.11.2
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
optionalDependencies:
'@vue/devtools-api': 6.6.3
@@ -15985,12 +16000,12 @@ snapshots:
- rollup
- supports-color
'@nuxt/vite-builder@3.13.0(@biomejs/biome@1.7.3)(@types/node@22.3.0)(eslint@9.9.1(jiti@1.21.6))(lightningcss@1.22.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(terser@5.31.6)(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2))':
'@nuxt/vite-builder@3.13.0(@biomejs/biome@1.7.3)(@types/node@22.3.0)(eslint@9.9.1(jiti@1.21.6))(lightningcss@1.22.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(terser@5.31.6)(typescript@5.6.2)(vue@3.5.6(typescript@5.6.2))':
dependencies:
'@nuxt/kit': 3.13.0(magicast@0.3.5)(rollup@4.21.2)
'@rollup/plugin-replace': 5.0.7(rollup@4.21.2)
'@vitejs/plugin-vue': 5.1.3(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.5(typescript@5.6.2))
'@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.5(typescript@5.6.2))
'@vitejs/plugin-vue': 5.1.3(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.6(typescript@5.6.2))
'@vitejs/plugin-vue-jsx': 4.0.1(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.6(typescript@5.6.2))
autoprefixer: 10.4.20(postcss@8.4.44)
clear: 0.1.0
consola: 3.2.3
@@ -16019,7 +16034,7 @@ snapshots:
vite: 5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6)
vite-node: 2.0.5(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6)
vite-plugin-checker: 0.7.2(@biomejs/biome@1.7.3)(eslint@9.9.1(jiti@1.21.6))(optionator@0.9.4)(typescript@5.6.2)(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
vue-bundle-renderer: 2.1.0
transitivePeerDependencies:
- '@biomejs/biome'
@@ -19916,13 +19931,13 @@ snapshots:
'@unhead/schema': 1.10.0
'@unhead/shared': 1.10.0
'@unhead/vue@1.10.0(vue@3.5.5(typescript@5.6.2))':
'@unhead/vue@1.10.0(vue@3.5.6(typescript@5.6.2))':
dependencies:
'@unhead/schema': 1.10.0
'@unhead/shared': 1.10.0
hookable: 5.5.3
unhead: 1.10.0
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
'@urql/core@2.3.6(graphql@15.8.0)':
dependencies:
@@ -20022,20 +20037,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.5(typescript@5.6.2))':
'@vitejs/plugin-vue-jsx@4.0.1(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.6(typescript@5.6.2))':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2)
'@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.25.2)
vite: 5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6)
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-vue@5.1.3(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.5(typescript@5.6.2))':
'@vitejs/plugin-vue@5.1.3(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))(vue@3.5.6(typescript@5.6.2))':
dependencies:
vite: 5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6)
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
'@vitest/expect@1.6.0':
dependencies:
@@ -20066,16 +20081,16 @@ snapshots:
loupe: 2.3.7
pretty-format: 29.7.0
'@vue-macros/common@1.12.2(rollup@4.21.2)(vue@3.5.5(typescript@5.6.2))':
'@vue-macros/common@1.12.2(rollup@4.21.2)(vue@3.5.6(typescript@5.6.2))':
dependencies:
'@babel/types': 7.25.6
'@rollup/pluginutils': 5.1.0(rollup@4.21.2)
'@vue/compiler-sfc': 3.5.4
'@vue/compiler-sfc': 3.5.5
ast-kit: 1.1.0
local-pkg: 0.5.0
magic-string-ast: 0.6.2
optionalDependencies:
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
transitivePeerDependencies:
- rollup
@@ -20106,7 +20121,7 @@ snapshots:
'@babel/helper-module-imports': 7.22.15
'@babel/helper-plugin-utils': 7.24.8
'@babel/parser': 7.25.6
'@vue/compiler-sfc': 3.5.4
'@vue/compiler-sfc': 3.5.5
'@vue/compiler-core@3.5.0':
dependencies:
@@ -20116,14 +20131,6 @@ snapshots:
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-core@3.5.4':
dependencies:
'@babel/parser': 7.25.6
'@vue/shared': 3.5.4
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-core@3.5.5':
dependencies:
'@babel/parser': 7.25.6
@@ -20132,21 +20139,29 @@ snapshots:
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-core@3.5.6':
dependencies:
'@babel/parser': 7.25.6
'@vue/shared': 3.5.6
entities: 4.5.0
estree-walker: 2.0.2
source-map-js: 1.2.0
'@vue/compiler-dom@3.5.0':
dependencies:
'@vue/compiler-core': 3.5.0
'@vue/shared': 3.5.0
'@vue/compiler-dom@3.5.4':
dependencies:
'@vue/compiler-core': 3.5.4
'@vue/shared': 3.5.4
'@vue/compiler-dom@3.5.5':
dependencies:
'@vue/compiler-core': 3.5.5
'@vue/shared': 3.5.5
'@vue/compiler-dom@3.5.6':
dependencies:
'@vue/compiler-core': 3.5.6
'@vue/shared': 3.5.6
'@vue/compiler-sfc@3.5.0':
dependencies:
'@babel/parser': 7.25.6
@@ -20159,18 +20174,6 @@ snapshots:
postcss: 8.4.44
source-map-js: 1.2.0
'@vue/compiler-sfc@3.5.4':
dependencies:
'@babel/parser': 7.25.6
'@vue/compiler-core': 3.5.4
'@vue/compiler-dom': 3.5.4
'@vue/compiler-ssr': 3.5.4
'@vue/shared': 3.5.4
estree-walker: 2.0.2
magic-string: 0.30.11
postcss: 8.4.44
source-map-js: 1.2.0
'@vue/compiler-sfc@3.5.5':
dependencies:
'@babel/parser': 7.25.6
@@ -20183,21 +20186,33 @@ snapshots:
postcss: 8.4.44
source-map-js: 1.2.0
'@vue/compiler-sfc@3.5.6':
dependencies:
'@babel/parser': 7.25.6
'@vue/compiler-core': 3.5.6
'@vue/compiler-dom': 3.5.6
'@vue/compiler-ssr': 3.5.6
'@vue/shared': 3.5.6
estree-walker: 2.0.2
magic-string: 0.30.11
postcss: 8.4.47
source-map-js: 1.2.0
'@vue/compiler-ssr@3.5.0':
dependencies:
'@vue/compiler-dom': 3.5.0
'@vue/shared': 3.5.0
'@vue/compiler-ssr@3.5.4':
dependencies:
'@vue/compiler-dom': 3.5.4
'@vue/shared': 3.5.4
'@vue/compiler-ssr@3.5.5':
dependencies:
'@vue/compiler-dom': 3.5.5
'@vue/shared': 3.5.5
'@vue/compiler-ssr@3.5.6':
dependencies:
'@vue/compiler-dom': 3.5.6
'@vue/shared': 3.5.6
'@vue/devtools-api@6.6.3': {}
'@vue/devtools-core@7.3.3(vite@5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6))':
@@ -20229,19 +20244,19 @@ snapshots:
dependencies:
'@vue/shared': 3.5.0
'@vue/reactivity@3.5.5':
'@vue/reactivity@3.5.6':
dependencies:
'@vue/shared': 3.5.5
'@vue/shared': 3.5.6
'@vue/runtime-core@3.5.0':
dependencies:
'@vue/reactivity': 3.5.0
'@vue/shared': 3.5.0
'@vue/runtime-core@3.5.5':
'@vue/runtime-core@3.5.6':
dependencies:
'@vue/reactivity': 3.5.5
'@vue/shared': 3.5.5
'@vue/reactivity': 3.5.6
'@vue/shared': 3.5.6
'@vue/runtime-dom@3.5.0':
dependencies:
@@ -20250,11 +20265,11 @@ snapshots:
'@vue/shared': 3.5.0
csstype: 3.1.3
'@vue/runtime-dom@3.5.5':
'@vue/runtime-dom@3.5.6':
dependencies:
'@vue/reactivity': 3.5.5
'@vue/runtime-core': 3.5.5
'@vue/shared': 3.5.5
'@vue/reactivity': 3.5.6
'@vue/runtime-core': 3.5.6
'@vue/shared': 3.5.6
csstype: 3.1.3
'@vue/server-renderer@3.5.0(vue@3.5.0(typescript@5.6.0-beta))':
@@ -20263,26 +20278,26 @@ snapshots:
'@vue/shared': 3.5.0
vue: 3.5.0(typescript@5.6.0-beta)
'@vue/server-renderer@3.5.5(vue@3.5.5(typescript@5.5.4))':
'@vue/server-renderer@3.5.6(vue@3.5.6(typescript@5.5.4))':
dependencies:
'@vue/compiler-ssr': 3.5.5
'@vue/shared': 3.5.5
vue: 3.5.5(typescript@5.5.4)
'@vue/compiler-ssr': 3.5.6
'@vue/shared': 3.5.6
vue: 3.5.6(typescript@5.5.4)
'@vue/server-renderer@3.5.5(vue@3.5.5(typescript@5.6.2))':
'@vue/server-renderer@3.5.6(vue@3.5.6(typescript@5.6.2))':
dependencies:
'@vue/compiler-ssr': 3.5.5
'@vue/shared': 3.5.5
vue: 3.5.5(typescript@5.6.2)
'@vue/compiler-ssr': 3.5.6
'@vue/shared': 3.5.6
vue: 3.5.6(typescript@5.6.2)
'@vue/shared@3.4.38': {}
'@vue/shared@3.5.0': {}
'@vue/shared@3.5.4': {}
'@vue/shared@3.5.5': {}
'@vue/shared@3.5.6': {}
'@web3-storage/multipart-parser@1.0.0': {}
'@webgpu/types@0.1.45': {}
@@ -20706,7 +20721,7 @@ snapshots:
base64-js@1.5.1: {}
better-auth@0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.5.4)(vue@3.5.5(typescript@5.5.4)):
better-auth@0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.5.4)(vue@3.5.6(typescript@5.5.4)):
dependencies:
'@better-fetch/fetch': 1.1.4
'@better-fetch/logger': 1.1.3
@@ -20714,7 +20729,7 @@ snapshots:
'@nanostores/query': 0.3.4(nanostores@0.11.2)
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@19.0.0-rc-7771d3a7-20240827)
'@nanostores/solid': 0.4.2(nanostores@0.11.2)(solid-js@1.8.21)
'@nanostores/vue': 0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.5(typescript@5.5.4))
'@nanostores/vue': 0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.6(typescript@5.5.4))
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.4.0
'@paralleldrive/cuid2': 2.2.2
@@ -20749,7 +20764,7 @@ snapshots:
- typescript
- vue
better-auth@0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2)):
better-auth@0.0.4(@vue/devtools-api@6.6.3)(react@19.0.0-rc-7771d3a7-20240827)(solid-js@1.8.21)(typescript@5.6.2)(vue@3.5.6(typescript@5.6.2)):
dependencies:
'@better-fetch/fetch': 1.1.4
'@better-fetch/logger': 1.1.3
@@ -20757,7 +20772,7 @@ snapshots:
'@nanostores/query': 0.3.4(nanostores@0.11.2)
'@nanostores/react': 0.7.3(nanostores@0.11.2)(react@19.0.0-rc-7771d3a7-20240827)
'@nanostores/solid': 0.4.2(nanostores@0.11.2)(solid-js@1.8.21)
'@nanostores/vue': 0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.5(typescript@5.6.2))
'@nanostores/vue': 0.10.0(@vue/devtools-api@6.6.3)(nanostores@0.11.2)(vue@3.5.6(typescript@5.6.2))
'@noble/ciphers': 0.6.0
'@noble/hashes': 1.4.0
'@paralleldrive/cuid2': 2.2.2
@@ -25814,10 +25829,10 @@ snapshots:
'@nuxt/kit': 3.13.0(magicast@0.3.5)(rollup@4.21.2)
'@nuxt/schema': 3.13.0(rollup@4.21.2)
'@nuxt/telemetry': 2.5.4(magicast@0.3.5)(rollup@4.21.2)
'@nuxt/vite-builder': 3.13.0(@biomejs/biome@1.7.3)(@types/node@22.3.0)(eslint@9.9.1(jiti@1.21.6))(lightningcss@1.22.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(terser@5.31.6)(typescript@5.6.2)(vue@3.5.5(typescript@5.6.2))
'@nuxt/vite-builder': 3.13.0(@biomejs/biome@1.7.3)(@types/node@22.3.0)(eslint@9.9.1(jiti@1.21.6))(lightningcss@1.22.0)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.21.2)(terser@5.31.6)(typescript@5.6.2)(vue@3.5.6(typescript@5.6.2))
'@unhead/dom': 1.10.0
'@unhead/ssr': 1.10.0
'@unhead/vue': 1.10.0(vue@3.5.5(typescript@5.6.2))
'@unhead/vue': 1.10.0(vue@3.5.6(typescript@5.6.2))
'@vue/shared': 3.4.38
acorn: 8.12.1
c12: 1.11.1(magicast@0.3.5)
@@ -25861,13 +25876,13 @@ snapshots:
unenv: 1.10.0
unimport: 3.11.1(rollup@4.21.2)
unplugin: 1.12.2
unplugin-vue-router: 0.10.7(rollup@4.21.2)(vue-router@4.4.3(vue@3.5.5(typescript@5.6.2)))(vue@3.5.5(typescript@5.6.2))
unplugin-vue-router: 0.10.7(rollup@4.21.2)(vue-router@4.4.3(vue@3.5.6(typescript@5.6.2)))(vue@3.5.6(typescript@5.6.2))
unstorage: 1.10.2(ioredis@5.4.1)
untyped: 1.4.2
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
vue-bundle-renderer: 2.1.0
vue-devtools-stub: 0.1.0
vue-router: 4.4.3(vue@3.5.5(typescript@5.6.2))
vue-router: 4.4.3(vue@3.5.6(typescript@5.6.2))
optionalDependencies:
'@parcel/watcher': 2.4.1
'@types/node': 22.3.0
@@ -26301,6 +26316,8 @@ snapshots:
picocolors@1.0.1: {}
picocolors@1.1.0: {}
picomatch@2.3.1: {}
picomatch@3.0.1: {}
@@ -26422,12 +26439,21 @@ snapshots:
postcss: 8.4.44
ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.6.2)
postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0)(yaml@2.5.0):
postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)):
dependencies:
lilconfig: 3.1.2
yaml: 2.5.0
optionalDependencies:
postcss: 8.4.47
ts-node: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)
optional: true
postcss-load-config@6.0.1(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.0)(yaml@2.5.0):
dependencies:
lilconfig: 3.1.2
optionalDependencies:
jiti: 1.21.6
postcss: 8.4.44
postcss: 8.4.47
tsx: 4.19.0
yaml: 2.5.0
@@ -26583,6 +26609,12 @@ snapshots:
picocolors: 1.0.1
source-map-js: 1.2.0
postcss@8.4.47:
dependencies:
nanoid: 3.3.7
picocolors: 1.1.0
source-map-js: 1.2.1
postgres-array@2.0.0: {}
postgres-array@3.0.2: {}
@@ -27008,6 +27040,12 @@ snapshots:
qr.js: 0.0.0
react: 18.3.1
react-qr-code@2.0.15(react@19.0.0-rc-7771d3a7-20240827):
dependencies:
prop-types: 15.8.1
qr.js: 0.0.0
react: 19.0.0-rc-7771d3a7-20240827
react-reconciler@0.27.0(react@19.0.0-rc-7771d3a7-20240827):
dependencies:
loose-envify: 1.4.0
@@ -27845,6 +27883,8 @@ snapshots:
source-map-js@1.2.0: {}
source-map-js@1.2.1: {}
source-map-support@0.5.21:
dependencies:
buffer-from: 1.1.2
@@ -28126,14 +28166,14 @@ snapshots:
dependencies:
react: 19.0.0-rc-7771d3a7-20240827
svelte-check@3.8.6(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.44)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.44)(svelte@4.2.19):
svelte-check@3.8.6(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.47)(svelte@4.2.19):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
chokidar: 3.6.0
picocolors: 1.0.1
sade: 1.8.1
svelte: 4.2.19
svelte-preprocess: 5.1.4(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.44)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.44)(svelte@4.2.19)(typescript@5.6.2)
svelte-preprocess: 5.1.4(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.47)(svelte@4.2.19)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
@@ -28150,7 +28190,7 @@ snapshots:
dependencies:
svelte: 4.2.19
svelte-preprocess@5.1.4(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.44)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.44)(svelte@4.2.19)(typescript@5.6.2):
svelte-preprocess@5.1.4(@babel/core@7.25.2)(postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4)))(postcss@8.4.47)(svelte@4.2.19)(typescript@5.6.2):
dependencies:
'@types/pug': 2.0.10
detect-indent: 6.1.0
@@ -28160,8 +28200,8 @@ snapshots:
svelte: 4.2.19
optionalDependencies:
'@babel/core': 7.25.2
postcss: 8.4.44
postcss-load-config: 4.0.2(postcss@8.4.44)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4))
postcss: 8.4.47
postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.5.4))
typescript: 5.6.2
svelte@4.2.19:
@@ -28625,7 +28665,7 @@ snapshots:
tslib@2.6.3: {}
tsup@8.2.4(@swc/core@1.7.26(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0)(typescript@5.6.0-beta)(yaml@2.5.0):
tsup@8.2.4(@swc/core@1.7.26(@swc/helpers@0.5.13))(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.0)(typescript@5.6.0-beta)(yaml@2.5.0):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
@@ -28637,7 +28677,7 @@ snapshots:
globby: 11.1.0
joycon: 3.1.1
picocolors: 1.0.1
postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.44)(tsx@4.19.0)(yaml@2.5.0)
postcss-load-config: 6.0.1(jiti@1.21.6)(postcss@8.4.47)(tsx@4.19.0)(yaml@2.5.0)
resolve-from: 5.0.0
rollup: 4.21.2
source-map: 0.8.0-beta.0
@@ -28645,7 +28685,7 @@ snapshots:
tree-kill: 1.2.2
optionalDependencies:
'@swc/core': 1.7.26(@swc/helpers@0.5.13)
postcss: 8.4.44
postcss: 8.4.47
typescript: 5.6.0-beta
transitivePeerDependencies:
- jiti
@@ -28940,11 +28980,11 @@ snapshots:
unpipe@1.0.0: {}
unplugin-vue-router@0.10.7(rollup@4.21.2)(vue-router@4.4.3(vue@3.5.5(typescript@5.6.2)))(vue@3.5.5(typescript@5.6.2)):
unplugin-vue-router@0.10.7(rollup@4.21.2)(vue-router@4.4.3(vue@3.5.6(typescript@5.6.2)))(vue@3.5.6(typescript@5.6.2)):
dependencies:
'@babel/types': 7.25.6
'@rollup/pluginutils': 5.1.0(rollup@4.21.2)
'@vue-macros/common': 1.12.2(rollup@4.21.2)(vue@3.5.5(typescript@5.6.2))
'@vue-macros/common': 1.12.2(rollup@4.21.2)(vue@3.5.6(typescript@5.6.2))
ast-walker-scope: 0.6.2
chokidar: 3.6.0
fast-glob: 3.3.2
@@ -28957,7 +28997,7 @@ snapshots:
unplugin: 1.12.2
yaml: 2.5.0
optionalDependencies:
vue-router: 4.4.3(vue@3.5.5(typescript@5.6.2))
vue-router: 4.4.3(vue@3.5.6(typescript@5.6.2))
transitivePeerDependencies:
- rollup
- vue
@@ -29361,7 +29401,7 @@ snapshots:
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2)
'@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.25.2)
'@vue/babel-plugin-jsx': 1.2.2(@babel/core@7.25.2)
'@vue/compiler-dom': 3.5.4
'@vue/compiler-dom': 3.5.5
kolorist: 1.8.0
magic-string: 0.30.11
vite: 5.4.2(@types/node@22.3.0)(lightningcss@1.22.0)(terser@5.31.6)
@@ -29449,10 +29489,10 @@ snapshots:
vue-devtools-stub@0.1.0: {}
vue-router@4.4.3(vue@3.5.5(typescript@5.6.2)):
vue-router@4.4.3(vue@3.5.6(typescript@5.6.2)):
dependencies:
'@vue/devtools-api': 6.6.3
vue: 3.5.5(typescript@5.6.2)
vue: 3.5.6(typescript@5.6.2)
vue@3.5.0(typescript@5.6.0-beta):
dependencies:
@@ -29464,23 +29504,23 @@ snapshots:
optionalDependencies:
typescript: 5.6.0-beta
vue@3.5.5(typescript@5.5.4):
vue@3.5.6(typescript@5.5.4):
dependencies:
'@vue/compiler-dom': 3.5.5
'@vue/compiler-sfc': 3.5.5
'@vue/runtime-dom': 3.5.5
'@vue/server-renderer': 3.5.5(vue@3.5.5(typescript@5.5.4))
'@vue/shared': 3.5.5
'@vue/compiler-dom': 3.5.6
'@vue/compiler-sfc': 3.5.6
'@vue/runtime-dom': 3.5.6
'@vue/server-renderer': 3.5.6(vue@3.5.6(typescript@5.5.4))
'@vue/shared': 3.5.6
optionalDependencies:
typescript: 5.5.4
vue@3.5.5(typescript@5.6.2):
vue@3.5.6(typescript@5.6.2):
dependencies:
'@vue/compiler-dom': 3.5.5
'@vue/compiler-sfc': 3.5.5
'@vue/runtime-dom': 3.5.5
'@vue/server-renderer': 3.5.5(vue@3.5.5(typescript@5.6.2))
'@vue/shared': 3.5.5
'@vue/compiler-dom': 3.5.6
'@vue/compiler-sfc': 3.5.6
'@vue/runtime-dom': 3.5.6
'@vue/server-renderer': 3.5.6(vue@3.5.6(typescript@5.6.2))
'@vue/shared': 3.5.6
optionalDependencies:
typescript: 5.6.2