mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-10 04:19:32 +00:00
wip
This commit is contained in:
@@ -31,9 +31,9 @@
|
|||||||
--chart-5: 27 87% 67%
|
--chart-5: 27 87% 67%
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
--background: 360 50% 5%;
|
--background: 0 0% 2%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 240 10% 0%;
|
||||||
--card-foreground: 0 0% 98%;
|
--card-foreground: 0 0% 98%;
|
||||||
--popover: 240 10% 3.9%;
|
--popover: 240 10% 3.9%;
|
||||||
--popover-foreground: 0 0% 98%;
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|||||||
@@ -1,15 +1,39 @@
|
|||||||
import { PlusIcon } from "lucide-react";
|
import {
|
||||||
|
Code,
|
||||||
|
Layout,
|
||||||
|
LayoutDashboard,
|
||||||
|
Mail,
|
||||||
|
PhoneCall,
|
||||||
|
PlusIcon,
|
||||||
|
Users,
|
||||||
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../ui/dialog";
|
} from "../ui/dialog";
|
||||||
import { Card } from "../ui/card";
|
import {
|
||||||
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "../ui/card";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||||
|
import SignIn from "./sign-in";
|
||||||
|
import { SignUp } from "./sign-up";
|
||||||
|
import { AuthTabs } from "./tabs";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Switch } from "../ui/switch";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
|
||||||
export function Builder() {
|
export function Builder() {
|
||||||
|
const enabledComp = {};
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -24,33 +48,104 @@ export function Builder() {
|
|||||||
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-stone-800/90 to-emerald-400/0 transition-opacity duration-500 group-hover:opacity-40"></span>
|
<span className="absolute -bottom-0 left-[1.125rem] h-px w-[calc(100%-2.25rem)] bg-gradient-to-r from-emerald-400/0 via-stone-800/90 to-emerald-400/0 transition-opacity duration-500 group-hover:opacity-40"></span>
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent className="max-w-7xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Create Sign in Box</DialogTitle>
|
<DialogTitle>Create Sign in Box</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Configure the sign in box to your liking and copy the code to your
|
||||||
|
application
|
||||||
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Card className="relative h-full w-full bg-transparent max-w-7xl mx-auto rounded-none">
|
|
||||||
<div className="w-full border-b-2 border-gray-200/50 dark:border-gray-900/50">
|
<div className="flex gap-12">
|
||||||
<div className="overflow-hidden md:ml-[-2px] bg-transparent flex gap-10 items-center justify-between md:justify-normal rounded-none">
|
<div className="w-4/12">
|
||||||
<Tabs defaultValue="preview" className="w-full">
|
<AuthTabs
|
||||||
<TabsList className=" md:ml-[-5px] data-[state=active]:bg-background items-center justify-between md:justify-normal bg-tranparent gap-3 w-full md:w-fit rounded-none">
|
tabs={[
|
||||||
<TabsTrigger
|
{
|
||||||
className="rounded-none py-2 pt-4 data-[state=active]:text-white flex items-center gap-2 data-[state=active]:bg-stone-900 "
|
title: "Sign In",
|
||||||
value="preview"
|
value: "sign-in",
|
||||||
onClick={() => {
|
content: <SignIn />,
|
||||||
// setIsPrev(true);
|
},
|
||||||
// setActiveTab("preview");
|
{
|
||||||
}}
|
title: "Sign Up",
|
||||||
|
value: "sign-up",
|
||||||
|
content: <SignUp />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow">
|
||||||
|
<AuthTabs
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
title: "editor",
|
||||||
|
value: "config",
|
||||||
|
content: (
|
||||||
|
<Card className="rounded-none">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Configuration</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<Label>Email & Password</Label>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Mail size={16} />
|
||||||
|
<Label>Email</Label>
|
||||||
|
</div>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Users size={16} />
|
||||||
|
<Label>Username</Label>
|
||||||
|
</div>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<PhoneCall size={16} />
|
||||||
|
<Label>Phone Number</Label>
|
||||||
|
</div>
|
||||||
|
<Switch />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
{/* <Layout className="w-4 h-4" /> */}
|
<path
|
||||||
<span className="py-1 flex items-center justify-center">
|
fill="currentColor"
|
||||||
Preview
|
d="M3 17h18q.425 0 .713.288T22 18t-.288.713T21 19H3q-.425 0-.712-.288T2 18t.288-.712T3 17m1-5.55l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T1 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T7 9.95t-.213.538t-.537.212H5.3l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T9 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .537.213T15 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35zm8 0l-.475.85q-.15.275-.45.35t-.575-.075t-.35-.45t.075-.575l.475-.85h-.95q-.325 0-.537-.212T17 9.95t.213-.537t.537-.213h.95l-.475-.8q-.15-.275-.075-.575t.35-.45t.575-.075t.45.35l.475.8l.475-.8q.15-.275.45-.35t.575.075t.35.45t-.075.575l-.475.8h.95q.325 0 .538.213T23 9.95t-.213.538t-.537.212h-.95l.475.85q.15.275.075.575t-.35.45t-.575.075t-.45-.35z"
|
||||||
</span>
|
></path>
|
||||||
</TabsTrigger>
|
</svg>
|
||||||
</TabsList>
|
<Label>Password</Label>
|
||||||
</Tabs>
|
</div>
|
||||||
|
<Switch />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button variant="secondary">Copy Code</Button>
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "code",
|
||||||
|
value: "auth.ts",
|
||||||
|
content: <div></div>,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
223
docs/components/buidler/sign-in.tsx
Normal file
223
docs/components/buidler/sign-in.tsx
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||||
|
import { Key, Loader2 } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function SignIn() {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
return (
|
||||||
|
<Card className="z-50 rounded-none max-w-md">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg md:text-xl">Sign In</CardTitle>
|
||||||
|
<CardDescription className="text-xs md:text-sm">
|
||||||
|
Enter your email below to login to your account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="m@example.com"
|
||||||
|
required
|
||||||
|
onChange={(e) => {
|
||||||
|
setEmail(e.target.value);
|
||||||
|
}}
|
||||||
|
value={email}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
<Link
|
||||||
|
href="/forget-password"
|
||||||
|
className="ml-auto inline-block text-sm underline"
|
||||||
|
>
|
||||||
|
Forgot your password?
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="password"
|
||||||
|
autoComplete="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
onClick={() => {
|
||||||
|
setRememberMe(!rememberMe);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label>Remember me</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={loading}
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
{loading ? <Loader2 size={16} className="animate-spin" /> : "Login"}
|
||||||
|
</Button>
|
||||||
|
<div className="grid grid-cols-4 gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<GitHubLogoIcon />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<DiscordLogoIcon />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className=" gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="0.98em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 256 262"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="#4285F4"
|
||||||
|
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#34A853"
|
||||||
|
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FBBC05"
|
||||||
|
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#EB4335"
|
||||||
|
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 3h9v9H2zm9 19H2v-9h9zM21 3v9h-9V3zm0 19h-9v-9h9z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M11.64 5.93h1.43v4.28h-1.43m3.93-4.28H17v4.28h-1.43M7 2L3.43 5.57v12.86h4.28V22l3.58-3.57h2.85L20.57 12V2m-1.43 9.29l-2.85 2.85h-2.86l-2.5 2.5v-2.5H7.71V3.43h11.43Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.3em"
|
||||||
|
height="1.3em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M22 12c0-5.52-4.48-10-10-10S2 6.48 2 12c0 4.84 3.44 8.87 8 9.8V15H8v-3h2V9.5C10 7.57 11.57 6 13.5 6H16v3h-2c-.55 0-1 .45-1 1v2h3v3h-3v6.95c5.05-.5 9-4.76 9-9.95"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 14 14"
|
||||||
|
>
|
||||||
|
<g fill="none">
|
||||||
|
<g clipPath="url(#primeTwitter0)">
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M11.025.656h2.147L8.482 6.03L14 13.344H9.68L6.294 8.909l-3.87 4.435H.275l5.016-5.75L0 .657h4.43L7.486 4.71zm-.755 11.4h1.19L3.78 1.877H2.504z"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="primeTwitter0">
|
||||||
|
<path fill="#fff" d="M0 0h14v14H0z"></path>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" className="gap-2" onClick={async () => {}}>
|
||||||
|
<Key size={16} />
|
||||||
|
Sign-in with Passkey
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<div className="flex justify-center w-full border-t py-4">
|
||||||
|
<p className="text-center text-xs text-neutral-500">
|
||||||
|
Secured by <span className="text-orange-400">better-auth.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
282
docs/components/buidler/sign-up.tsx
Normal file
282
docs/components/buidler/sign-up.tsx
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
"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 { DiscordLogoIcon, GitHubLogoIcon } from "@radix-ui/react-icons";
|
||||||
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Loader2, X } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
export function SignUp() {
|
||||||
|
const [firstName, setFirstName] = useState("");
|
||||||
|
const [lastName, setLastName] = useState("");
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [passwordConfirmation, setPasswordConfirmation] = useState("");
|
||||||
|
const [image, setImage] = useState<File | null>(null);
|
||||||
|
const [imagePreview, setImagePreview] = useState<string | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setImage(file);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
setImagePreview(reader.result as string);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="z-50 rounded-md rounded-t-none max-w-md">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-lg md:text-xl">Sign Up</CardTitle>
|
||||||
|
<CardDescription className="text-xs md:text-sm">
|
||||||
|
Enter your information to create an account
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="first-name">First name</Label>
|
||||||
|
<Input
|
||||||
|
id="first-name"
|
||||||
|
placeholder="Max"
|
||||||
|
required
|
||||||
|
onChange={(e) => {
|
||||||
|
setFirstName(e.target.value);
|
||||||
|
}}
|
||||||
|
value={firstName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="last-name">Last name</Label>
|
||||||
|
<Input
|
||||||
|
id="last-name"
|
||||||
|
placeholder="Robinson"
|
||||||
|
required
|
||||||
|
onChange={(e) => {
|
||||||
|
setLastName(e.target.value);
|
||||||
|
}}
|
||||||
|
value={lastName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="m@example.com"
|
||||||
|
required
|
||||||
|
onChange={(e) => {
|
||||||
|
setEmail(e.target.value);
|
||||||
|
}}
|
||||||
|
value={email}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="password">Password</Label>
|
||||||
|
{/* <PasswordInput
|
||||||
|
id="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
autoComplete="new-password"
|
||||||
|
placeholder="Password"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="password">Confirm Password</Label>
|
||||||
|
{/* <PasswordInput
|
||||||
|
id="password_confirmation"
|
||||||
|
value={passwordConfirmation}
|
||||||
|
onChange={(e) => setPasswordConfirmation(e.target.value)}
|
||||||
|
autoComplete="new-password"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<Label htmlFor="image">Profile Image (optional)</Label>
|
||||||
|
<div className="flex items-end gap-4">
|
||||||
|
{imagePreview && (
|
||||||
|
<div className="relative w-16 h-16 rounded-sm overflow-hidden">
|
||||||
|
<Image
|
||||||
|
src={imagePreview}
|
||||||
|
alt="Profile preview"
|
||||||
|
layout="fill"
|
||||||
|
objectFit="cover"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
<Input
|
||||||
|
id="image"
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleImageChange}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
{imagePreview && (
|
||||||
|
<X
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setImage(null);
|
||||||
|
setImagePreview(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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",
|
||||||
|
// fetchOptions: {
|
||||||
|
// onResponse: () => {
|
||||||
|
// setLoading(false);
|
||||||
|
// },
|
||||||
|
// onRequest: () => {
|
||||||
|
// setLoading(true);
|
||||||
|
// },
|
||||||
|
// onError: (ctx) => {
|
||||||
|
// toast.error(ctx.error.message);
|
||||||
|
// },
|
||||||
|
// onSuccess: async () => {
|
||||||
|
// router.push("/dashboard");
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Loader2 size={16} className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
"Create an account"
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
// await signIn.social({
|
||||||
|
// provider: "github",
|
||||||
|
// callbackURL: "/dashboard",
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GitHubLogoIcon />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
// await signIn.social({
|
||||||
|
// provider: "discord",
|
||||||
|
// callbackURL: "/dashboard",
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DiscordLogoIcon />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
// await signIn.social({
|
||||||
|
// provider: "google",
|
||||||
|
// callbackURL: "/dashboard",
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="0.98em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 256 262"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="#4285F4"
|
||||||
|
d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622l38.755 30.023l2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#34A853"
|
||||||
|
d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055c-34.523 0-63.824-22.773-74.269-54.25l-1.531.13l-40.298 31.187l-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#FBBC05"
|
||||||
|
d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82c0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="#EB4335"
|
||||||
|
d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="w-full gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
// await signIn.social({
|
||||||
|
// provider: "microsoft",
|
||||||
|
// callbackURL: "/dashboard",
|
||||||
|
// });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1.2em"
|
||||||
|
height="1.2em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 3h9v9H2zm9 19H2v-9h9zM21 3v9h-9V3zm0 19h-9v-9h9z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<div className="flex justify-center w-full border-t py-4">
|
||||||
|
<p className="text-center text-xs text-neutral-500">
|
||||||
|
Secured by <span className="text-orange-400">better-auth.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertImageToBase64(file: File): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => resolve(reader.result as string);
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
118
docs/components/buidler/tabs.tsx
Normal file
118
docs/components/buidler/tabs.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
type Tab = {
|
||||||
|
title: string;
|
||||||
|
value: string;
|
||||||
|
content?: string | React.ReactNode | any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthTabs = ({
|
||||||
|
tabs: propTabs,
|
||||||
|
containerClassName,
|
||||||
|
activeTabClassName,
|
||||||
|
tabClassName,
|
||||||
|
}: {
|
||||||
|
tabs: Tab[];
|
||||||
|
containerClassName?: string;
|
||||||
|
activeTabClassName?: string;
|
||||||
|
tabClassName?: string;
|
||||||
|
}) => {
|
||||||
|
const [active, setActive] = useState<Tab>(propTabs[0]);
|
||||||
|
const [tabs, setTabs] = useState<Tab[]>(propTabs);
|
||||||
|
const isActive = (tab: Tab) => {
|
||||||
|
return tab.value === tabs[0].value;
|
||||||
|
};
|
||||||
|
const moveSelectedTabToTop = (idx: number) => {
|
||||||
|
const newTabs = [...propTabs];
|
||||||
|
const selectedTab = newTabs.splice(idx, 1);
|
||||||
|
newTabs.unshift(selectedTab[0]);
|
||||||
|
setTabs(newTabs);
|
||||||
|
setActive(newTabs[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [hovering, setHovering] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-row items-center justify-start mt-0 [perspective:1000px] relative overflow-auto sm:overflow-visible no-visible-scrollbar border-x w-full border-t max-w-max bg-opacity-0",
|
||||||
|
containerClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{propTabs.map((tab, idx) => (
|
||||||
|
<button
|
||||||
|
key={tab.title}
|
||||||
|
onClick={() => {
|
||||||
|
moveSelectedTabToTop(idx);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHovering(true)}
|
||||||
|
onMouseLeave={() => setHovering(false)}
|
||||||
|
className={cn(
|
||||||
|
"relative px-4 py-2 rounded-full opacity-80 hover:opacity-100",
|
||||||
|
tabClassName,
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
transformStyle: "preserve-3d",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{active.value === tab.value && (
|
||||||
|
<motion.div
|
||||||
|
transition={{
|
||||||
|
duration: 0.2,
|
||||||
|
delay: 0.1,
|
||||||
|
|
||||||
|
type: "keyframes",
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
x: tabs.indexOf(tab) === 0 ? [0, 0, 0] : [0, 0, 0],
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
"absolute inset-0 bg-gray-200 dark:bg-zinc-900/90 opacity-100",
|
||||||
|
activeTabClassName,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"relative block text-black dark:text-white",
|
||||||
|
active.value === tab.value
|
||||||
|
? "text-opacity-100 font-medium"
|
||||||
|
: "opacity-40 ",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tab.title}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
{tabs.map((tab, idx) => (
|
||||||
|
<motion.div
|
||||||
|
key={tab.value}
|
||||||
|
style={{
|
||||||
|
scale: 1 - idx * 0.1,
|
||||||
|
zIndex: -idx,
|
||||||
|
opacity: idx < 3 ? 1 - idx * 0.1 : 0,
|
||||||
|
}}
|
||||||
|
animate={{
|
||||||
|
transition: {
|
||||||
|
duration: 0.2,
|
||||||
|
delay: 0.1,
|
||||||
|
type: "keyframes",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className={cn("w-50 h-full", isActive(tab) ? "" : "hidden")}
|
||||||
|
>
|
||||||
|
{tab.content}
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-none text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const Checkbox = React.forwardRef<
|
|||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
"peer h-4 w-4 shrink-0 rounded-none border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-9 w-full rounded-none-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
Reference in New Issue
Block a user