mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
feat: sign-in box
This commit is contained in:
61
docs/components/builder/code-tabs/code-editor.tsx
Normal file
61
docs/components/builder/code-tabs/code-editor.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Highlight, themes } from "prism-react-renderer";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import theme from "./theme";
|
||||
|
||||
interface CodeEditorProps {
|
||||
code: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export function CodeEditor({ code, language }: CodeEditorProps) {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
} catch (err) {
|
||||
console.error("Failed to copy text: ", err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Highlight theme={theme} code={code} language={language}>
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||
<pre
|
||||
className={`${className} p-4 overflow-auto max-h-[400px] rounded-md`}
|
||||
style={style}
|
||||
>
|
||||
{tokens.map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line, key: i })}>
|
||||
<span className="mr-4 text-gray-500 select-none">{i + 1}</span>
|
||||
{line.map((token, key) => (
|
||||
<span key={key} {...getTokenProps({ token, key })} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</pre>
|
||||
)}
|
||||
</Highlight>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="absolute top-2 right-2"
|
||||
onClick={copyToClipboard}
|
||||
aria-label="Copy code"
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="h-4 w-4" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
55
docs/components/builder/code-tabs/code-tabs.tsx
Normal file
55
docs/components/builder/code-tabs/code-tabs.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
interface TabProps {
|
||||
fileName: string;
|
||||
isActive: boolean;
|
||||
brightnessLevel?: number; // New optional prop for brightness level
|
||||
onClick: () => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const brightnessLevels = [
|
||||
"bg-background",
|
||||
"bg-background-200", //
|
||||
"bg-background-300",
|
||||
"bg-background-400",
|
||||
"bg-background-500",
|
||||
"bg-background-600",
|
||||
"bg-background-700",
|
||||
];
|
||||
|
||||
export function CodeTab({
|
||||
fileName,
|
||||
isActive,
|
||||
brightnessLevel = 0,
|
||||
onClick,
|
||||
onClose,
|
||||
}: TabProps) {
|
||||
const activeBrightnessClass = isActive
|
||||
? brightnessLevels[brightnessLevel % brightnessLevels.length]
|
||||
: "bg-muted";
|
||||
|
||||
const textColor = isActive ? "text-foreground" : "text-muted-foreground";
|
||||
const borderColor = isActive
|
||||
? "border-t-foreground"
|
||||
: "border-t-transparent hover:bg-background/50";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center px-3 py-2 text-sm font-medium border-t-2 cursor-pointer transition-colors duration-200",
|
||||
activeBrightnessClass,
|
||||
textColor,
|
||||
borderColor,
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="truncate max-w-[100px]">{fileName}</span>
|
||||
<button className="ml-2 text-muted-foreground hover:text-foreground transition-colors duration-200">
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
147
docs/components/builder/code-tabs/index.tsx
Normal file
147
docs/components/builder/code-tabs/index.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import React, { useState } from "react";
|
||||
import { TabBar } from "./tab-bar";
|
||||
import { CodeEditor } from "./code-editor";
|
||||
import { useAtom } from "jotai";
|
||||
import { optionsAtom } from "../store";
|
||||
import { js_beautify } from "js-beautify";
|
||||
import { singUpString } from "../sign-up";
|
||||
import { signInString } from "../sign-in";
|
||||
|
||||
export default function CodeTabs() {
|
||||
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.forgetPassword
|
||||
? `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, "")},`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
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(),` : ""
|
||||
}],`
|
||||
: ""
|
||||
}
|
||||
})
|
||||
|
||||
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: singUpString,
|
||||
});
|
||||
}
|
||||
|
||||
const [files, setFiles] = useState(initialFiles);
|
||||
const [activeFileId, setActiveFileId] = useState(files[0].id);
|
||||
|
||||
const handleTabClick = (fileId: string) => {
|
||||
setActiveFileId(fileId);
|
||||
};
|
||||
|
||||
const handleTabClose = (fileId: string) => {
|
||||
setFiles(files.filter((file) => file.id !== fileId));
|
||||
if (activeFileId === fileId) {
|
||||
setActiveFileId(files[0].id);
|
||||
}
|
||||
};
|
||||
|
||||
const activeFile = files.find((file) => file.id === activeFileId);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-3xl mx-auto mt-8 border border-border rounded-md overflow-hidden">
|
||||
<TabBar
|
||||
files={files}
|
||||
activeFileId={activeFileId}
|
||||
onTabClick={handleTabClick}
|
||||
onTabClose={handleTabClose}
|
||||
/>
|
||||
<div className="bg-background">
|
||||
{activeFile && (
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={
|
||||
activeFile.name.endsWith(".ts")
|
||||
? js_beautify(activeFile.content)
|
||||
: activeFile.content.replace(/\n{3,}/g, "\n\n")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
docs/components/builder/code-tabs/tab-bar.tsx
Normal file
36
docs/components/builder/code-tabs/tab-bar.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { CodeTab } from "./code-tabs";
|
||||
|
||||
interface File {
|
||||
id: string;
|
||||
name: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface TabBarProps {
|
||||
files: File[];
|
||||
activeFileId: string;
|
||||
onTabClick: (fileId: string) => void;
|
||||
onTabClose: (fileId: string) => void;
|
||||
}
|
||||
|
||||
export function TabBar({
|
||||
files,
|
||||
activeFileId,
|
||||
onTabClick,
|
||||
onTabClose,
|
||||
}: TabBarProps) {
|
||||
return (
|
||||
<div className="flex bg-muted border-b border-border">
|
||||
{files.map((file) => (
|
||||
<CodeTab
|
||||
key={file.id}
|
||||
fileName={file.name}
|
||||
isActive={file.id === activeFileId}
|
||||
onClick={() => onTabClick(file.id)}
|
||||
onClose={() => onTabClose(file.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
79
docs/components/builder/code-tabs/theme.ts
Normal file
79
docs/components/builder/code-tabs/theme.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { PrismTheme } from "prism-react-renderer";
|
||||
|
||||
const theme: PrismTheme = {
|
||||
plain: {
|
||||
color: "#d0d0d0",
|
||||
backgroundColor: "#000000", // Changed to true black
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
types: ["comment", "prolog", "doctype", "cdata"],
|
||||
style: {
|
||||
color: "#555555",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["namespace"],
|
||||
style: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["string", "attr-value"],
|
||||
style: {
|
||||
color: "#8ab4f8", // Darker soft blue for strings
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["punctuation", "operator"],
|
||||
style: {
|
||||
color: "#888888",
|
||||
},
|
||||
},
|
||||
{
|
||||
types: [
|
||||
"entity",
|
||||
"url",
|
||||
"symbol",
|
||||
"number",
|
||||
"boolean",
|
||||
"variable",
|
||||
"constant",
|
||||
"property",
|
||||
"regex",
|
||||
"inserted",
|
||||
],
|
||||
style: {
|
||||
color: "#a0a0a0",
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["atrule", "keyword", "attr-name", "selector"],
|
||||
style: {
|
||||
color: "#c5c5c5",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["function", "deleted", "tag"],
|
||||
style: {
|
||||
color: "#7aa2f7", // Darker soft blue for functions
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["function-variable"],
|
||||
style: {
|
||||
color: "#9e9e9e",
|
||||
},
|
||||
},
|
||||
{
|
||||
types: ["tag", "selector", "keyword"],
|
||||
style: {
|
||||
color: "#cccccc", // Adjusted to a slightly lighter gray for better contrast on true black
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default theme;
|
||||
Reference in New Issue
Block a user