UNPKG

aau-auth-kit-ui

Version:

Plug & play shadcn/ui components for aau-auth-kit with Next.js integration

667 lines (653 loc) 20.8 kB
// src/lib/auth-view-paths.ts var authViewPaths = { callback: "callback", forgotPassword: "forgot-password", resetPassword: "reset-password", settings: "settings", signIn: "sign-in", signOut: "sign-out", signUp: "sign-up", twoFactor: "two-factor", signInPhone: "sign-in-phone", sendOtp: "send-otp" }; // src/lib/auth-ui-provider.tsx import { createContext, useMemo } from "react"; import { toast } from "sonner"; // src/hooks/use-auth-data.ts import { useCallback, useContext, useEffect, useRef, useState } from "react"; function useAuthData({ queryFn }) { const { authClient, toast: toast2 } = useContext(AuthUIContext); const { data: sessionData, isPending: sessionPending } = authClient.useSession(); const [data, setData] = useState(null); const [isPending, setIsPending] = useState(true); const initialized = useRef(false); const refetch = useCallback(async () => { const { data: data2, error } = await queryFn(); if (error) toast2({ variant: "error", message: error.message || "An error occurred" }); setData(data2); setIsPending(false); }, [queryFn, toast2]); useEffect(() => { if (!sessionData) { setIsPending(sessionPending); setData(null); initialized.current = false; return; } if (initialized.current) return; initialized.current = true; refetch(); }, [refetch, sessionData, sessionPending]); return { data, isPending, refetch }; } // src/lib/auth-ui-provider.tsx import { jsx } from "react/jsx-runtime"; var defaultRole = [ { key: "owner", value: "Owner" }, { key: "member", value: "Member" }, { key: "admin", value: "Admin" } ]; var DefaultLink = ({ href, className, children }) => /* @__PURE__ */ jsx("a", { className, href, children }); var defaultNavigate = (href) => { window.location.href = href; }; var defaultPathname = () => window.location.pathname; var defaultReplace = (href) => { window.location.replace(href); }; var defaultToast = ({ variant = "default", message }) => { if (variant === "default") { toast(message); } else { toast[variant](message); } }; var AuthUIContext = createContext( {} ); var AuthUIProvider = ({ children, authClient, avatarExtension = "png", avatarSize, basePath = "/auth", baseURL = "", redirectTo = "/", credentials = true, forgotPassword = true, freshAge = 60 * 60 * 24, hooks: hooksProp, mutators: mutatorsProp, nameRequired = true, settingsFields = ["name"], signUp = true, signUpFields = ["name"], toast: toast2 = defaultToast, viewPaths: viewPathsProp, navigate, replace, uploadAvatar, Link = DefaultLink, pathname, adminRole, roles, ...props }) => { const defaultMutators = useMemo(() => { return { revokeSession: (params) => authClient.revokeSession({ ...params, fetchOptions: { throw: true } }), updateUser: (params) => authClient.updateUser({ ...params, fetchOptions: { throw: true } }) }; }, [authClient]); const defaultHooks = useMemo(() => { return { useSession: authClient.useSession, useListAccounts: () => useAuthData({ queryFn: authClient.listAccounts }), useListSessions: () => useAuthData({ queryFn: authClient.listSessions }) }; }, [authClient]); const viewPaths = useMemo(() => { return { ...authViewPaths, ...viewPathsProp }; }, [viewPathsProp]); const hooks = useMemo(() => { return { ...defaultHooks, ...hooksProp }; }, [defaultHooks, hooksProp]); const mutators = useMemo(() => { return { ...defaultMutators, ...mutatorsProp }; }, [defaultMutators, mutatorsProp]); baseURL = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL; basePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath; const admin = adminRole ?? "admin"; const finalRoles = roles ?? defaultRole; return /* @__PURE__ */ jsx( AuthUIContext.Provider, { value: { roles: finalRoles, authClient, avatarExtension, avatarSize: avatarSize || (uploadAvatar ? 256 : 128), basePath: basePath === "/" ? "" : basePath, baseURL, redirectTo, credentials, forgotPassword, freshAge, hooks, mutators, nameRequired, settingsFields, signUp, signUpFields, toast: toast2, navigate: navigate || defaultNavigate, pathname: pathname || defaultPathname, replace: replace || navigate || defaultReplace, viewPaths, uploadAvatar, Link, adminRole: admin, ...props }, children } ); }; // src/components/ui/button.tsx import { Slot } from "@radix-ui/react-slot"; import { cva } from "class-variance-authority"; // src/lib/utils.ts import { clsx } from "clsx"; import { twMerge } from "tailwind-merge"; function cn(...inputs) { return twMerge(clsx(inputs)); } function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } function errorCodeToCamelCase(errorCode) { return errorCode.toLowerCase().replace(/_([a-z])/g, (_, char) => char.toUpperCase()); } function getLocalizedError({ error, localization }) { if (error?.error) { if (error.error.code) { const camelCaseErrorCode = errorCodeToCamelCase( error.error.code ); if (localization?.[camelCaseErrorCode]) return localization[camelCaseErrorCode]; } return error.error.message || error.error.code || error.error.statusText || localization?.requestFailed; } return error?.message || localization?.requestFailed || "Request failed"; } function getSearchParam(paramName) { return typeof window !== "undefined" ? new URLSearchParams(window.location.search).get(paramName) : null; } function getAuthViewByPath(authViewPaths2, path) { for (const authViewPathsKey in authViewPaths2) { if (authViewPaths2[authViewPathsKey] === path) { return authViewPathsKey; } } } // src/components/ui/button.tsx import { jsx as jsx2 } from "react/jsx-runtime"; var buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9" } }, defaultVariants: { variant: "default", size: "default" } } ); function Button({ className, variant, size, asChild = false, ...props }) { const Comp = asChild ? Slot : "button"; return /* @__PURE__ */ jsx2( Comp, { "data-slot": "button", className: cn(buttonVariants({ variant, size, className })), ...props } ); } // src/components/ui/input.tsx import { jsx as jsx3 } from "react/jsx-runtime"; function Input({ className, type, ...props }) { return /* @__PURE__ */ jsx3( "input", { type, "data-slot": "input", className: cn( "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className ), ...props } ); } // src/components/ui/dialog.tsx import * as DialogPrimitive from "@radix-ui/react-dialog"; import { XIcon } from "lucide-react"; import { jsx as jsx4, jsxs } from "react/jsx-runtime"; function Dialog({ ...props }) { return /* @__PURE__ */ jsx4(DialogPrimitive.Root, { "data-slot": "dialog", ...props }); } function DialogPortal({ ...props }) { return /* @__PURE__ */ jsx4(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props }); } function DialogClose({ ...props }) { return /* @__PURE__ */ jsx4(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props }); } function DialogOverlay({ className, ...props }) { return /* @__PURE__ */ jsx4( DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: cn( "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", className ), ...props } ); } function DialogContent({ className, children, ...props }) { return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [ /* @__PURE__ */ jsx4(DialogOverlay, {}), /* @__PURE__ */ jsxs( DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn( "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", className ), ...props, children: [ children, /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", children: [ /* @__PURE__ */ jsx4(XIcon, {}), /* @__PURE__ */ jsx4("span", { className: "sr-only", children: "Close" }) ] }) ] } ) ] }); } function DialogHeader({ className, ...props }) { return /* @__PURE__ */ jsx4( "div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-2 text-center sm:text-left", className), ...props } ); } function DialogFooter({ className, ...props }) { return /* @__PURE__ */ jsx4( "div", { "data-slot": "dialog-footer", className: cn( "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className ), ...props } ); } function DialogTitle({ className, ...props }) { return /* @__PURE__ */ jsx4( DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn("text-lg leading-none font-semibold", className), ...props } ); } function DialogDescription({ className, ...props }) { return /* @__PURE__ */ jsx4( DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("text-muted-foreground text-sm", className), ...props } ); } // src/components/ui/form.tsx import * as React from "react"; import { Slot as Slot2 } from "@radix-ui/react-slot"; import { Controller, FormProvider, useFormContext, useFormState } from "react-hook-form"; // src/components/ui/label.tsx import * as LabelPrimitive from "@radix-ui/react-label"; import { jsx as jsx5 } from "react/jsx-runtime"; function Label({ className, ...props }) { return /* @__PURE__ */ jsx5( LabelPrimitive.Root, { "data-slot": "label", className: cn( "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className ), ...props } ); } // src/components/ui/form.tsx import { jsx as jsx6 } from "react/jsx-runtime"; var Form = FormProvider; var FormFieldContext = React.createContext( {} ); var FormField = ({ ...props }) => { return /* @__PURE__ */ jsx6(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx6(Controller, { ...props }) }); }; var useFormField = () => { const fieldContext = React.useContext(FormFieldContext); const itemContext = React.useContext(FormItemContext); const { getFieldState } = useFormContext(); const formState = useFormState({ name: fieldContext.name }); const fieldState = getFieldState(fieldContext.name, formState); if (!fieldContext) { throw new Error("useFormField should be used within <FormField>"); } const { id } = itemContext; return { id, name: fieldContext.name, formItemId: `${id}-form-item`, formDescriptionId: `${id}-form-item-description`, formMessageId: `${id}-form-item-message`, ...fieldState }; }; var FormItemContext = React.createContext( {} ); function FormItem({ className, ...props }) { const id = React.useId(); return /* @__PURE__ */ jsx6(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx6( "div", { "data-slot": "form-item", className: cn("grid gap-2", className), ...props } ) }); } function FormLabel({ className, ...props }) { const { error, formItemId } = useFormField(); return /* @__PURE__ */ jsx6( Label, { "data-slot": "form-label", "data-error": !!error, className: cn("data-[error=true]:text-destructive", className), htmlFor: formItemId, ...props } ); } function FormControl({ ...props }) { const { error, formItemId, formDescriptionId, formMessageId } = useFormField(); return /* @__PURE__ */ jsx6( Slot2, { "data-slot": "form-control", id: formItemId, "aria-describedby": !error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`, "aria-invalid": !!error, ...props } ); } function FormMessage({ className, ...props }) { const { error, formMessageId } = useFormField(); const body = error ? String(error?.message ?? "") : props.children; if (!body) { return null; } return /* @__PURE__ */ jsx6( "p", { "data-slot": "form-message", id: formMessageId, className: cn("text-destructive text-sm", className), ...props, children: body } ); } // src/components/ui/select.tsx import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown, ChevronUp } from "lucide-react"; import * as React2 from "react"; import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime"; var Select = SelectPrimitive.Root; var SelectValue = SelectPrimitive.Value; var SelectTrigger = React2.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2( SelectPrimitive.Trigger, { ref, className: cn( "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className ), ...props, children: [ children, /* @__PURE__ */ jsx7(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx7(ChevronDown, { className: "h-4 w-4 opacity-50" }) }) ] } )); SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; var SelectScrollUpButton = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7( SelectPrimitive.ScrollUpButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ jsx7(ChevronUp, { className: "h-4 w-4" }) } )); SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; var SelectScrollDownButton = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7( SelectPrimitive.ScrollDownButton, { ref, className: cn( "flex cursor-default items-center justify-center py-1", className ), ...props, children: /* @__PURE__ */ jsx7(ChevronDown, { className: "h-4 w-4" }) } )); SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; var SelectContent = React2.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx7(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs2( SelectPrimitive.Content, { ref, className: cn( "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className ), position, ...props, children: [ /* @__PURE__ */ jsx7(SelectScrollUpButton, {}), /* @__PURE__ */ jsx7( SelectPrimitive.Viewport, { className: cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]" ), children } ), /* @__PURE__ */ jsx7(SelectScrollDownButton, {}) ] } ) })); SelectContent.displayName = SelectPrimitive.Content.displayName; var SelectLabel = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7( SelectPrimitive.Label, { ref, className: cn("px-2 py-1.5 text-sm font-semibold", className), ...props } )); SelectLabel.displayName = SelectPrimitive.Label.displayName; var SelectItem = React2.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2( SelectPrimitive.Item, { ref, className: cn( "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className ), ...props, children: [ /* @__PURE__ */ jsx7("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx7(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx7(Check, { className: "h-4 w-4" }) }) }), /* @__PURE__ */ jsx7(SelectPrimitive.ItemText, { children }) ] } )); SelectItem.displayName = SelectPrimitive.Item.displayName; var SelectSeparator = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx7( SelectPrimitive.Separator, { ref, className: cn("-mx-1 my-1 h-px bg-muted", className), ...props } )); SelectSeparator.displayName = SelectPrimitive.Separator.displayName; export { authViewPaths, AuthUIContext, AuthUIProvider, cn, isValidEmail, getLocalizedError, getSearchParam, getAuthViewByPath, Button, Input, Dialog, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, Label, Form, FormField, FormItem, FormLabel, FormControl, FormMessage, Select, SelectValue, SelectTrigger, SelectContent, SelectItem };