From 372af1c51e2d5ce4834efa5a2ec354bf325bfedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A9l=20Solano?= Date: Wed, 14 Jan 2026 22:22:14 +0100 Subject: [PATCH] docs: improve create sign in box (#7349) --- .../builder/code-tabs/code-tabs.tsx | 27 + .../builder/code-tabs/frameworks/nextjs.ts | 596 +++++++++++++ .../builder/code-tabs/frameworks/nuxt.ts | 582 ++++++++++++ .../code-tabs/frameworks/svelte-kit.ts | 577 ++++++++++++ docs/components/builder/code-tabs/index.tsx | 121 +-- docs/components/builder/index.tsx | 844 +++++++++++------- docs/components/builder/sign-in.tsx | 277 +----- docs/components/builder/sign-up.tsx | 209 +---- docs/components/builder/store.ts | 8 +- docs/components/builder/tabs.tsx | 4 +- 10 files changed, 2316 insertions(+), 929 deletions(-) create mode 100644 docs/components/builder/code-tabs/frameworks/nextjs.ts create mode 100644 docs/components/builder/code-tabs/frameworks/nuxt.ts create mode 100644 docs/components/builder/code-tabs/frameworks/svelte-kit.ts diff --git a/docs/components/builder/code-tabs/code-tabs.tsx b/docs/components/builder/code-tabs/code-tabs.tsx index 128695b0d2..60698d3064 100644 --- a/docs/components/builder/code-tabs/code-tabs.tsx +++ b/docs/components/builder/code-tabs/code-tabs.tsx @@ -71,6 +71,33 @@ export function CodeTab({ > )} + {fileName.endsWith(".vue") && ( + + + + )} + {fileName.endsWith(".svelte") && ( + + + + )} {fileName} ); diff --git a/docs/components/builder/code-tabs/frameworks/nextjs.ts b/docs/components/builder/code-tabs/frameworks/nextjs.ts new file mode 100644 index 0000000000..e3826142f9 --- /dev/null +++ b/docs/components/builder/code-tabs/frameworks/nextjs.ts @@ -0,0 +1,596 @@ +import { socialProviders } from "../../social-provider"; +import type { SignInBoxOptions } from "../../store"; + +export function resolveNextJSFiles(options: SignInBoxOptions) { + const files = [ + { + id: "1", + name: "auth.ts", + content: `import { betterAuth } from "better-auth"; +import { nextCookies } from "better-auth/next";${ + options.magicLink + ? ` +import { magicLink } from "better-auth/plugins";` + : "" + }${ + options.passkey + ? ` +import { passkey } from "@better-auth/passkey";` + : "" + } + +export const auth = betterAuth({ + ${ + options.email + ? `emailAndPassword: { + enabled: true, +${ + options.requestPasswordReset + ? `async sendResetPassword(data, request) { + // Send an email to the user with a link to reset their password + },` + : `` +} + },` + : "" + }${ + options.socialProviders.length + ? `socialProviders: ${JSON.stringify( + options.socialProviders.reduce( + (acc, provider) => ({ + ...acc, + [provider]: { + clientId: `process.env.${provider.toUpperCase()}_CLIENT_ID!`, + clientSecret: `process.env.${provider.toUpperCase()}_CLIENT_SECRET!`, + }, + }), + {}, + ), + ).replace(/"/g, "")},` + : "" + } + plugins: [${ + options.magicLink + ? ` + magicLink({ + async sendMagicLink(data) { + // Send an email to the user with a magic link + }, + }),` + : "" + }${ + options.passkey + ? ` + passkey(),` + : "" + } + nextCookies(), + ], + /** if no database is provided, the user data will be stored in memory. + * Make sure to provide a database to persist user data **/ + }); + `, + }, + { + id: "2", + name: "auth-client.ts", + content: `import { createAuthClient } from "better-auth/react";${ + options.magicLink + ? ` + import { magicLinkClient } from "better-auth/client/plugins";` + : "" + }${ + options.passkey + ? ` + import { passkeyClient } from "@better-auth/passkey/client";` + : "" + } + + export const authClient = createAuthClient({ + baseURL: process.env.NEXT_PUBLIC_APP_URL,${ + options.magicLink || options.passkey + ? ` + plugins: [${options.magicLink ? `magicLinkClient()${options.passkey ? "," : ""}` : ""}${ + options.passkey ? `passkeyClient()` : "" + }],` + : "" + } + }); + + export const { signIn, signOut, signUp, useSession } = authClient; + `, + }, + { + id: "3", + name: "sign-in.tsx", + content: signInString(options), + }, + ]; + + if (options.signUp) { + files.push({ + id: "4", + name: "sign-up.tsx", + content: signUpString(options), + }); + } + + return files; +} + +const signInString = (options: SignInBoxOptions) => `"use client" + +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { useState } from "react"; +import { Loader2, Key } from "lucide-react"; +import { signIn } from "@/lib/auth-client"; +import Link from "next/link"; +import { cn } from "@/lib/utils"; + +export default function SignIn() {${ + options.email || options.magicLink + ? ` + const [email, setEmail] = useState("");` + : "" +}${ + options.email + ? ` + const [password, setPassword] = useState("");` + : "" +} + const [loading, setLoading] = useState(false);${ + options.rememberMe + ? ` + const [rememberMe, setRememberMe] = useState(false);` + : "" + } + + return ( + + + Sign In + + Enter your email below to login to your account + + + +
+ ${ + options.email + ? `
+ + { + setEmail(e.target.value); + }} + value={email} + /> +
+ +
+
+ ${ + options.requestPasswordReset + ? ` + + Forgot your password? + ` + : "" + } +
+ + setPassword(e.target.value)} + /> +
${ + options.rememberMe + ? ` +
+ { + setRememberMe(!rememberMe); + }} + /> + +
` + : "" + }` + : "" + }${ + options.magicLink + ? `
+ + { + setEmail(e.target.value); + }} + value={email} + /> + +
` + : "" + }${ + options.email + ? ` + ` + : "" + }${ + options.passkey + ? ` + ` + : "" + }${ + options.socialProviders?.length > 0 + ? ` +
3 + ? '"justify-between flex-wrap"' + : '"justify-between flex-col"' + } + )}> + ${options.socialProviders + .map((provider: string) => { + const icon = + socialProviders[provider as keyof typeof socialProviders] + ?.stringIcon || ""; + return ``; + }) + .join("\n\t\t\t\t\t\t")} +
` + : "" + } +
+
+ ${ + options.label + ? ` +
+

+ built with{" "} + + + better-auth. + + +

+
+
` + : "" + } +
+ ); +}`; + +const signUpString = (options: SignInBoxOptions) => `"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 { useState } from "react"; +import Image from "next/image"; +import { Loader2, X } from "lucide-react"; +import { signUp } from "@/lib/auth-client"; +import { toast } from "sonner"; +import { useRouter } from "next/navigation"; + +export default 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(null); + const [imagePreview, setImagePreview] = useState(null); + const router = useRouter(); + const [loading, setLoading] = useState(false); + + const handleImageChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setImage(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); + } + }; + + return ( + + + Sign Up + + Enter your information to create an account + + + +
+
+
+ + { + setFirstName(e.target.value); + }} + value={firstName} + /> +
+
+ + { + setLastName(e.target.value); + }} + value={lastName} + /> +
+
+
+ + { + setEmail(e.target.value); + }} + value={email} + /> +
+
+ + setPassword(e.target.value)} + autoComplete="new-password" + placeholder="Password" + /> +
+
+ + setPasswordConfirmation(e.target.value)} + autoComplete="new-password" + placeholder="Confirm Password" + /> +
+
+ +
+ {imagePreview && ( +
+ Profile preview +
+ )} +
+ + {imagePreview && ( + { + setImage(null); + setImagePreview(null); + }} + /> + )} +
+
+
+ +
+
${ + options.label + ? ` + +
+

+ Secured by better-auth. +

+
+
` + : "" + } +
+ ); +} + +async function convertImageToBase64(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => resolve(reader.result as string); + reader.onerror = reject; + reader.readAsDataURL(file); + }); +}`; diff --git a/docs/components/builder/code-tabs/frameworks/nuxt.ts b/docs/components/builder/code-tabs/frameworks/nuxt.ts new file mode 100644 index 0000000000..716472e708 --- /dev/null +++ b/docs/components/builder/code-tabs/frameworks/nuxt.ts @@ -0,0 +1,582 @@ +import { socialProviders } from "../../social-provider"; +import type { SignInBoxOptions } from "../../store"; + +export function resolveNuxtFiles(options: SignInBoxOptions) { + const files = [ + { + id: "1", + name: "auth.ts", + content: `import { betterAuth } from "better-auth";${ + options.magicLink + ? ` + import { magicLink } from "better-auth/plugins/magic-link";` + : "" + }${ + options.passkey + ? ` + import { passkey } from "@better-auth/passkey";` + : "" + } + + export const auth = betterAuth({ + ${ + options.email + ? `emailAndPassword: { + enabled: true, + ${ + options.requestPasswordReset + ? `async sendResetPassword(data, request) { + // Send an email to the user with a link to reset their password + },` + : `` + } + },` + : "" + }${ + options.socialProviders.length + ? `socialProviders: ${JSON.stringify( + options.socialProviders.reduce( + (acc, provider) => ({ + ...acc, + [provider]: { + clientId: `process.env.${provider.toUpperCase()}_CLIENT_ID!`, + clientSecret: `process.env.${provider.toUpperCase()}_CLIENT_SECRET!`, + }, + }), + {}, + ), + ).replace(/"/g, "")},` + : "" + }${ + options.magicLink || options.passkey + ? ` + plugins: [ + ${ + options.magicLink + ? `magicLink({ + async sendMagicLink(data) { + // Send an email to the user with a magic link + }, + }),` + : `${options.passkey ? `passkey(),` : ""}` + } + ${options.passkey && options.magicLink ? `passkey(),` : ""}],` + : "" + } + /** if no database is provided, the user data will be stored in memory. + * Make sure to provide a database to persist user data **/ + }); + `, + }, + { + id: "2", + name: "auth-client.ts", + content: `import { createAuthClient } from "better-auth/react";${ + options.magicLink + ? ` + import { magicLinkClient } from "better-auth/client/plugins";` + : "" + }${ + options.passkey + ? ` + import { passkeyClient } from "@better-auth/passkey/client";` + : "" + } + + export const authClient = createAuthClient({ + baseURL: process.env.NUXT_PUBLIC_APP_URL,${ + options.magicLink || options.passkey + ? ` + plugins: [${options.magicLink ? `magicLinkClient()${options.passkey ? "," : ""}` : ""}${ + options.passkey ? `passkeyClient()` : "" + }],` + : "" + } + }); + + export const { signIn, signOut, signUp, useSession } = authClient; + `, + }, + { + id: "3", + name: "sign-in.vue", + content: signInString(options), + }, + ]; + if (options.signUp) { + files.push({ + id: "4", + name: "sign-up.vue", + content: signUpString(options), + }); + } + + return files; +} + +const signInString = (options: SignInBoxOptions) => ` + + +`; + +const signUpString = (options: SignInBoxOptions) => ` + + +`; diff --git a/docs/components/builder/code-tabs/frameworks/svelte-kit.ts b/docs/components/builder/code-tabs/frameworks/svelte-kit.ts new file mode 100644 index 0000000000..c06ae2378c --- /dev/null +++ b/docs/components/builder/code-tabs/frameworks/svelte-kit.ts @@ -0,0 +1,577 @@ +import { socialProviders } from "../../social-provider"; +import type { SignInBoxOptions } from "../../store"; + +export function resolveSvelteKitFiles(options: SignInBoxOptions) { + const files = [ + { + id: "1", + name: "auth.ts", + content: `import { betterAuth } from "better-auth"; +import { sveltekitCookies } from "better-auth/svelte-kit"; +import { getRequestEvent } from "$app/server";${ + options.socialProviders.length > 0 + ? ` +import { + ${options.socialProviders + .map( + (provider) => `${provider.toUpperCase()}_CLIENT_ID, + ${provider.toUpperCase()}_CLIENT_SECRET`, + ) + .join("\n\t")} +} from "$env/static/private";` + : "" + }${ + options.magicLink + ? ` +import { magicLink } from "better-auth/plugins/magic-link";` + : "" + }${ + options.passkey + ? ` +import { passkey } from "@better-auth/passkey";` + : "" + } + +export const auth = betterAuth({ + ${ + options.email + ? `emailAndPassword: { + enabled: true, +${ + options.requestPasswordReset + ? `async sendResetPassword(data, request) { + // Send an email to the user with a link to reset their password + },` + : `` +} + },` + : "" + }${ + options.socialProviders.length + ? `socialProviders: ${JSON.stringify( + options.socialProviders.reduce( + (acc, provider) => ({ + ...acc, + [provider]: { + clientId: `${provider.toUpperCase()}_CLIENT_ID`, + clientSecret: `${provider.toUpperCase()}_CLIENT_SECRET`, + }, + }), + {}, + ), + ).replace(/"/g, "")},` + : "" + } + plugins: [${ + options.magicLink + ? ` + magicLink({ + async sendMagicLink(data) { + // Send an email to the user with a magic link + }, + }),` + : "" + }${ + options.passkey + ? ` + passkey(),` + : "" + } + sveltekitCookies(getRequestEvent), + ], + /** if no database is provided, the user data will be stored in memory. + * Make sure to provide a database to persist user data **/ +}); + `, + }, + { + id: "2", + name: "auth-client.ts", + content: `import { createAuthClient } from "better-auth/svelte"; + import { PUBLIC_APP_URL } from "$env/static/public";${ + options.magicLink + ? ` + import { magicLinkClient } from "better-auth/client/plugins";` + : "" + }${ + options.passkey + ? ` + import { passkeyClient } from "@better-auth/passkey/client";` + : "" + } + + export const authClient = createAuthClient({ + baseURL: PUBLIC_APP_URL,${ + options.magicLink || options.passkey + ? ` + plugins: [${options.magicLink ? `magicLinkClient()${options.passkey ? "," : ""}` : ""}${ + options.passkey ? `passkeyClient()` : "" + }],` + : "" + } + }); + + export const { signIn, signOut, signUp, useSession } = authClient; + `, + }, + { + id: "3", + name: "sign-in.svelte", + content: signInString(options), + }, + ]; + + if (options.signUp) { + files.push({ + id: "4", + name: "sign-up.svelte", + content: signUpString(options), + }); + } + + return files; +} + +const signInString = (options: SignInBoxOptions) => ` + + + + Sign In + + Enter your email below to login to your account + + + +
+ ${ + options.email + ? `
+ + +
+ +
+
+ ${ + options.requestPasswordReset + ? ` + + Forgot your password? + ` + : "" + } +
+ + +
${ + options.rememberMe + ? ` +
+ + +
` + : "" + }` + : "" + }${ + options.magicLink + ? `
+ + + +
` + : "" + }${ + options.email + ? ` + ` + : "" + }${ + options.passkey + ? ` + ` + : "" + }${ + options.socialProviders?.length > 0 + ? ` +
3 + ? '"justify-between flex-wrap"' + : '"justify-between flex-col"' + } + )}> + ${options.socialProviders + .map((provider: string) => { + const icon = + socialProviders[provider as keyof typeof socialProviders] + ?.stringIcon || ""; + return ``; + }) + .join("\n\t\t\t\t")} +
` + : "" + } +
+
+ ${ + options.label + ? ` +
+

+ built with  + + + better-auth. + + +

+
+
` + : "" + } +
+`; + +const signUpString = (options: SignInBoxOptions) => ` + + + + Sign Up + + Enter your information to create an account + + + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ {#if imagePreview} +
+ Profile preview
+								class= +
+ {/if} +
+ + {#if imagePreview} + { + image = null; + imagePreview = null; + }} + /> + {/if} +
+
+
+ +
+
${ + options.label + ? ` + +
+

+ Secured by better-auth. +

+
+
` + : "" + } +
`; diff --git a/docs/components/builder/code-tabs/index.tsx b/docs/components/builder/code-tabs/index.tsx index d316240ede..190ca4193e 100644 --- a/docs/components/builder/code-tabs/index.tsx +++ b/docs/components/builder/code-tabs/index.tsx @@ -1,112 +1,32 @@ import { useAtom } from "jotai"; import { js_beautify } from "js-beautify"; -import { useState } from "react"; -import { signInString } from "../sign-in"; -import { signUpString } from "../sign-up"; +import { useMemo, useState } from "react"; import { optionsAtom } from "../store"; import { CodeEditor } from "./code-editor"; +import { resolveNextJSFiles } from "./frameworks/nextjs"; +import { resolveNuxtFiles } from "./frameworks/nuxt"; +import { resolveSvelteKitFiles } from "./frameworks/svelte-kit"; import { TabBar } from "./tab-bar"; -export default function CodeTabs() { +export default function CodeTabs({ framework }: { framework: string }) { const [options] = useAtom(optionsAtom); - const initialFiles = [ - { - id: "1", - name: "auth.ts", - content: `import { betterAuth } from 'better-auth'; - - export const auth = betterAuth({ - ${ - options.email - ? `emailAndPassword: { - enabled: true, -${ - options.requestPasswordReset - ? `async sendResetPassword(data, request) { - // Send an email to the user with a link to reset their password - },` - : `` -} - },` - : "" - }${ - options.socialProviders.length - ? `socialProviders: ${JSON.stringify( - options.socialProviders.reduce((acc, provider) => { - return { - ...acc, - [provider]: { - clientId: `process.env.${provider.toUpperCase()}_CLIENT_ID!`, - clientSecret: `process.env.${provider.toUpperCase()}_CLIENT_SECRET!`, - }, - }; - }, {}), - ).replace(/"/g, "")},` - : "" + const files = useMemo(() => { + switch (framework) { + case "nextjs": + return resolveNextJSFiles(options); + case "nuxt": + return resolveNuxtFiles(options); + case "svelte-kit": + return resolveSvelteKitFiles(options); + case "solid-start": + break; } - ${ - options.magicLink || options.passkey - ? `plugins: [ - ${ - options.magicLink - ? `magicLink({ - async sendMagicLink(data) { - // Send an email to the user with a magic link - }, - }),` - : `${options.passkey ? `passkey(),` : ""}` - } - ${options.passkey && options.magicLink ? `passkey(),` : ""} - ]` - : "" - } - /** if no database is provided, the user data will be stored in memory. - * Make sure to provide a database to persist user data **/ - }); - `, - }, - { - id: "2", - name: "auth-client.ts", - content: `import { createAuthClient } from "better-auth/react"; - ${ - options.magicLink || options.passkey - ? `import { ${options.magicLink ? "magicLinkClient," : ""} ${ - options.passkey ? "passkeyClient" : "" - } } from "better-auth/client/plugins";` - : "" - } - export const authClient = createAuthClient({ - baseURL: process.env.NEXT_PUBLIC_APP_URL, - ${ - options.magicLink || options.passkey - ? `plugins: [${options.magicLink ? `magicLinkClient(),` : ""}${ - options.passkey ? `passkeyClient(),` : "" - }],` - : "" - } - }) + console.error("Invalid framework", framework); + return []; + }, [framework, options]); - export const { signIn, signOut, signUp, useSession } = authClient; - `, - }, - { - id: "3", - name: "sign-in.tsx", - content: signInString(options), - }, - ]; - if (options.email) { - initialFiles.push({ - id: "4", - name: "sign-up.tsx", - content: signUpString(options), - }); - } - - const [files, setFiles] = useState(initialFiles); const [activeFileId, setActiveFileId] = useState(files[0].id); const handleTabClick = (fileId: string) => { @@ -114,7 +34,6 @@ ${ }; const handleTabClose = (fileId: string) => { - setFiles(files.filter((file) => file.id !== fileId)); if (activeFileId === fileId) { setActiveFileId(files[0].id); } @@ -123,7 +42,7 @@ ${ const activeFile = files.find((file) => file.id === activeFileId); return ( -
+
{activeFile && ( ( @@ -51,6 +66,7 @@ const frameworks = [ ), }, { + id: "nuxt", title: "Nuxt", description: "The Intuitive Vue Framework", Icon: () => ( @@ -72,6 +88,7 @@ const frameworks = [ ), }, { + id: "svelte-kit", title: "SvelteKit", description: "Web development for the rest of us", Icon: () => ( @@ -104,8 +121,10 @@ const frameworks = [ ), }, { + id: "solid-start", title: "SolidStart", description: "Fine-grained reactivity goes fullstack", + disabled: true, Icon: () => ( (null); + + const setSocialProviderSearchInput = (value: string) => { + setSocialProviderSearchInputState(value); + + if (value === "") { + if (debounceTimer) { + clearTimeout(debounceTimer); + } + setDebouncedSearch(""); + setDebounceTimer(null); + return; + } + + if (debounceTimer) { + clearTimeout(debounceTimer); + } + + const id = window.setTimeout(() => { + setDebouncedSearch(value); + setDebounceTimer(null); + }, 300); + + setDebounceTimer(id); + }; + + const filteredSocialProviders = useMemo(() => { + const providers = Object.entries(socialProviders); + if (debouncedSearch.length === 0) { + return providers; + } + + return providers.filter(([name]) => + name.toLowerCase().includes(debouncedSearch.toLowerCase()), + ); + }, [debouncedSearch]); + + const resetSocialProviderSearch = () => { + setSocialProviderSearchInput(""); + }; + + const reset = () => { + setOptions(defaultOptions); + setFramework("nextjs"); + }; + + const isModified = useMemo(() => { + const optionKeys = Object.keys( + defaultOptions, + ) as (keyof typeof defaultOptions)[]; + return optionKeys.some((key) => { + const optionValue = options[key]; + const defaultValue = defaultOptions[key]; + if (Array.isArray(optionValue) && Array.isArray(defaultValue)) { + if (optionValue.length !== (defaultValue as any).length) { + return true; + } + return optionValue.some( + (val, index) => val !== (defaultValue as any)?.[index], + ); + } + return optionValue !== defaultValue; + }); + }, [debouncedSearch, options]); + return ( @@ -262,8 +351,8 @@ export function Builder() { - - + + Create Sign in Box Configure the sign in box to your liking and copy the code to your @@ -271,334 +360,356 @@ export function Builder() { -
-
-
- {options.signUp ? ( - , - }, - { - title: "Sign Up", - value: "sign-up", - content: , - }, - ]} - /> - ) : ( - - )} -
+
+
+ +
+ +
+
+
+ {options.signUp ? ( + , + }, + { + title: "Sign Up", + value: "sign-up", + content: , + }, + ]} + /> + ) : ( + + )} +
+
+
- -
- {currentStep === 0 ? ( - - - Configuration -
{ - if (resolvedTheme === "dark") { - setTheme("light"); - } else { - setTheme("dark"); - } - }} - > - {resolvedTheme === "dark" ? ( - setTheme("light")} size={18} /> - ) : ( - setTheme("dark")} size={18} /> - )} -
-
- -
-
- -
- -
-
- -
- { - setOptions((prev) => ({ - ...prev, - email: checked, - magicLink: checked ? false : prev.magicLink, - signUp: checked, - })); - }} - /> -
-
-
- -
- { - setOptions((prev) => ({ - ...prev, - rememberMe: checked, - })); - }} - /> -
-
-
- -
- { - setOptions((prev) => ({ - ...prev, - requestPasswordReset: checked, - })); - }} - /> -
-
-
-
- -
- - {Object.entries(socialProviders).map( - ([provider, { Icon }]) => ( -
-
- - -
- { - setOptions((prev) => ({ - ...prev, - socialProviders: checked - ? [...prev.socialProviders, provider] - : prev.socialProviders.filter( - (p) => p !== provider, - ), - })); - }} - /> -
- ), - )} -
-
-
- -
- -
-
- - -
- { - setOptions((prev) => ({ - ...prev, - passkey: checked, - })); - }} - /> -
- -
-
- - -
- { - setOptions((prev) => ({ - ...prev, - magicLink: checked, - email: checked ? false : prev.email, - signUp: checked ? false : prev.signUp, - })); - }} - /> -
-
-
- -
-
-
-
- - - -
- ) : currentStep === 1 ? ( - - - Choose Framework -

{ - setCurrentStep(0); - }} - > - Go Back -

-
- - {frameworks.map((fm) => ( -
{ - if (fm.title === "Next.js") { - setCurrentStep(currentStep + 1); - } - }} - className={cn( - "flex flex-col items-center gap-4 border p-6 rounded-md w-5/12 flex-grow h-44 relative", - fm.title !== "Next.js" - ? "opacity-55" - : "hover:ring-1 transition-all ring-border hover:bg-background duration-200 ease-in-out cursor-pointer", - )} - key={fm.title} + Reset + + )} + + + + - {fm.title !== "Next.js" && ( - - Coming Soon - - )} - - -

{fm.description}

-
- ))} -
-
- ) : ( - - -
- Code -
-
- -
-

- Copy the code below and paste it in your application to - get started. -

+ + Email & Password + +
+
+ +
+ { + setOptions((prev) => ({ + ...prev, + email: checked, + magicLink: checked ? false : prev.magicLink, + signUp: checked, + })); + }} + /> +
+
+
+ +
+ { + setOptions((prev) => ({ + ...prev, + rememberMe: checked, + })); + }} + /> +
+
+
+ +
+ { + setOptions((prev) => ({ + ...prev, + requestPasswordReset: checked, + })); + }} + /> +
+
+
+ + Social providers + +
+ + setSocialProviderSearchInput(e.target.value) + } + /> +
+ +
+ {socialProviderSearchInput?.length > 0 && ( + + )} +
+
+ {filteredSocialProviders + .sort(([a], [b]) => a.localeCompare(b)) + .map(([provider, { Icon }]) => ( + + ))} + {filteredSocialProviders.length === 0 && ( +
+ No providers found. + +
+ )} +
+
+
+ + Plugins + +
+
+ + +
+ { + setOptions((prev) => ({ + ...prev, + passkey: checked, + })); + }} + /> +
+ +
+
+ + +
+ { + setOptions((prev) => ({ + ...prev, + magicLink: checked, + email: checked ? false : prev.email, + signUp: checked ? false : prev.signUp, + })); + }} + /> +
+
+
+
+ + { + setOptions((prev) => ({ + ...prev, + label: checked, + })); + }} + /> +
+ + + + + + + + )} + + {currentStep === 1 && ( + <> + + Choose Framework

{ @@ -607,15 +718,66 @@ export function Builder() { > Go Back

-
-
- -
-
-
- )} -
-
+ + + {frameworks.map((fm) => ( +
{ + if (fm.disabled !== true) { + setCurrentStep(currentStep + 1); + setFramework(fm.id); + } + }} + className={cn( + "flex flex-col items-center gap-4 border p-6 rounded-md w-5/12 flex-grow h-44 relative", + fm.disabled === true + ? "opacity-55" + : "hover:ring-1 transition-all ring-border hover:bg-background duration-200 ease-in-out cursor-pointer", + )} + key={fm.id} + > + {fm.disabled === true && ( + + Coming Soon + + )} + + +

{fm.description}

+
+ ))} +
+ + )} + + {currentStep === 2 && ( + <> + + Code +

{ + setCurrentStep(0); + }} + > + Go Back +

+
+ + +
+

+ Copy the code below and paste it in your application + to get started. +

+
+ +
+ + )} + + +
diff --git a/docs/components/builder/sign-in.tsx b/docs/components/builder/sign-in.tsx index c62a3b9dd8..520fd35fe4 100644 --- a/docs/components/builder/sign-in.tsx +++ b/docs/components/builder/sign-in.tsx @@ -22,7 +22,7 @@ import { optionsAtom } from "./store"; export default function SignIn() { const [options] = useAtom(optionsAtom); return ( - + Sign In @@ -66,8 +66,8 @@ export default function SignIn() { {options.rememberMe && (
- - + +
)} @@ -156,274 +156,3 @@ export default function SignIn() {
); } - -export const signInString = (options: any) => `"use client" - -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Checkbox } from "@/components/ui/checkbox"; -import { useState } from "react"; -import { Loader2, Key } from "lucide-react"; -import { signIn } from "@/lib/auth-client"; -import Link from "next/link"; -import { cn } from "@/lib/utils"; - -export default function SignIn() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [loading, setLoading] = useState(false); - ${ - options.rememberMe - ? "const [rememberMe, setRememberMe] = useState(false);" - : "" - } - - return ( - - - Sign In - - Enter your email below to login to your account - - - -
- ${ - options.email - ? `
- - { - setEmail(e.target.value); - }} - value={email} - /> -
- -
-
- - ${ - options.requestPasswordReset - ? ` - Forgot your password? - ` - : "" - } -
- - setPassword(e.target.value)} - /> -
- - ${ - options.rememberMe - ? `
- { - setRememberMe(!rememberMe); - }} - /> - -
` - : "" - }` - : "" - } - - ${ - options.magicLink - ? `
- - { - setEmail(e.target.value); - }} - value={email} - /> - -
` - : "" - } - - ${ - options.email - ? `` - : "" - } - - ${ - options.passkey - ? `` - : "" - } - - ${ - options.socialProviders?.length > 0 - ? `
3 - ? '"justify-between flex-wrap"' - : '"justify-between flex-col"' - } - )}> - ${options.socialProviders - .map((provider: string) => { - const icon = - socialProviders[provider as keyof typeof socialProviders] - ?.stringIcon || ""; - return `\n\t\t\t\t`; - }) - .join("")} -
` - : "" - } -
-
- ${ - options.label - ? ` -
-

- built with{" "} - - - better-auth. - - -

-
-
` - : "" - } -
- ); -}`; diff --git a/docs/components/builder/sign-up.tsx b/docs/components/builder/sign-up.tsx index 137e2d83a2..290df9591b 100644 --- a/docs/components/builder/sign-up.tsx +++ b/docs/components/builder/sign-up.tsx @@ -41,7 +41,7 @@ export function SignUp() { const [loading] = useState(false); return ( - + Sign Up @@ -170,210 +170,3 @@ export function SignUp() { ); } - -export const signUpString = (options: any) => `"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 { useState } from "react"; -import Image from "next/image"; -import { Loader2, X } from "lucide-react"; -import { signUp } from "@/lib/auth-client"; -import { toast } from "sonner"; -import { useRouter } from "next/navigation"; - -export default 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(null); - const [imagePreview, setImagePreview] = useState(null); - const router = useRouter(); - const [loading, setLoading] = useState(false); - - const handleImageChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) { - setImage(file); - const reader = new FileReader(); - reader.onloadend = () => { - setImagePreview(reader.result as string); - }; - reader.readAsDataURL(file); - } - }; - - return ( - - - Sign Up - - Enter your information to create an account - - - -
-
-
- - { - setFirstName(e.target.value); - }} - value={firstName} - /> -
-
- - { - setLastName(e.target.value); - }} - value={lastName} - /> -
-
-
- - { - setEmail(e.target.value); - }} - value={email} - /> -
-
- - setPassword(e.target.value)} - autoComplete="new-password" - placeholder="Password" - /> -
-
- - setPasswordConfirmation(e.target.value)} - autoComplete="new-password" - placeholder="Confirm Password" - /> -
-
- -
- {imagePreview && ( -
- Profile preview -
- )} -
- - {imagePreview && ( - { - setImage(null); - setImagePreview(null); - }} - /> - )} -
-
-
- -
-
- ${ - options.label - ? ` -
-

- Secured by better-auth. -

-
-
` - : "" - } -
- ); -} - -async function convertImageToBase64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result as string); - reader.onerror = reject; - reader.readAsDataURL(file); - }); -}`; diff --git a/docs/components/builder/store.ts b/docs/components/builder/store.ts index ac89785cdd..9aff0ef101 100644 --- a/docs/components/builder/store.ts +++ b/docs/components/builder/store.ts @@ -1,6 +1,8 @@ import { atom } from "jotai"; -export const optionsAtom = atom({ +export type SignInBoxOptions = typeof defaultOptions; + +export const defaultOptions = { email: true, passkey: false, socialProviders: ["google", "github"], @@ -9,4 +11,6 @@ export const optionsAtom = atom({ label: true, rememberMe: true, requestPasswordReset: true, -}); +}; + +export const optionsAtom = atom(defaultOptions); diff --git a/docs/components/builder/tabs.tsx b/docs/components/builder/tabs.tsx index fa73c9a1b0..38f0f2ab11 100644 --- a/docs/components/builder/tabs.tsx +++ b/docs/components/builder/tabs.tsx @@ -36,9 +36,7 @@ export const AuthTabs = ({ tabs: propTabs }: { tabs: Tab[] }) => { onClick={() => { moveSelectedTabToTop(idx); }} - className={cn( - "relative px-4 py-2 rounded-full opacity-80 hover:opacity-100", - )} + className={cn("relative px-4 py-2 opacity-80 hover:opacity-100")} > {active.value === tab.value && (