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
JavaScript
// 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
};