@daveyplate/better-auth-ui
Version:
Plug & play shadcn/ui components for better-auth
826 lines (777 loc) • 26.1 kB
JavaScript
;Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } var _class;"use client"
var _chunk2IFYYEVLcjs = require('./chunk-2IFYYEVL.cjs');
// src/hooks/use-auth-data.ts
var _react = require('react');
// src/lib/auth-data-cache.ts
var AuthDataCache = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this); }
__init() {this.cache = /* @__PURE__ */ new Map()}
__init2() {this.listeners = /* @__PURE__ */ new Map()}
__init3() {this.inFlightRequests = /* @__PURE__ */ new Map()}
get(key) {
return this.cache.get(key);
}
set(key, data) {
const entry = {
data,
timestamp: Date.now(),
isRefetching: false
};
this.cache.set(key, entry);
this.notify(key);
}
setRefetching(key, isRefetching) {
const entry = this.cache.get(key);
if (entry) {
entry.isRefetching = isRefetching;
this.notify(key);
}
}
clear(key) {
if (key) {
this.cache.delete(key);
this.inFlightRequests.delete(key);
this.notify(key);
} else {
this.cache.clear();
this.inFlightRequests.clear();
const keys = Array.from(this.listeners.keys());
for (const key2 of keys) {
this.notify(key2);
}
}
}
getInFlightRequest(key) {
return this.inFlightRequests.get(key);
}
setInFlightRequest(key, promise) {
this.inFlightRequests.set(key, promise);
}
removeInFlightRequest(key) {
this.inFlightRequests.delete(key);
}
subscribe(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, /* @__PURE__ */ new Set());
}
this.listeners.get(key).add(callback);
return () => {
const callbacks = this.listeners.get(key);
if (callbacks) {
callbacks.delete(callback);
if (callbacks.size === 0) {
this.listeners.delete(key);
}
}
};
}
notify(key) {
const callbacks = this.listeners.get(key);
if (callbacks) {
const callbackArray = Array.from(callbacks);
for (const callback of callbackArray) {
callback();
}
}
}
}, _class);
var authDataCache = new AuthDataCache();
// src/lib/auth-ui-provider.tsx
var _sonner = require('sonner');
// src/components/captcha/recaptcha-v3.tsx
var _reactrecaptchav3 = require('@wojtekmaj/react-recaptcha-v3');
// src/hooks/use-hydrated.ts
function subscribe() {
return () => {
};
}
function useIsHydrated() {
return _react.useSyncExternalStore.call(void 0,
subscribe,
() => true,
() => false
);
}
// src/hooks/use-lang.ts
function useLang() {
const [lang, setLang] = _react.useState.call(void 0, );
_react.useEffect.call(void 0, () => {
const checkLang = () => {
const currentLang = document.documentElement.getAttribute("lang");
setLang(_nullishCoalesce(currentLang, () => ( void 0)));
};
checkLang();
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.attributeName === "lang") {
checkLang();
}
}
});
observer.observe(document.documentElement, { attributes: true });
return () => {
observer.disconnect();
};
}, []);
return { lang };
}
// src/hooks/use-theme.ts
function useTheme() {
const [theme, setTheme] = _react.useState.call(void 0, "light");
_react.useEffect.call(void 0, () => {
const checkTheme = () => {
var _a;
const isDark = document.documentElement.classList.contains("dark") || ((_a = document.documentElement.getAttribute("style")) == null ? void 0 : _a.includes("color-scheme: dark"));
setTheme(isDark ? "dark" : "light");
};
checkTheme();
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.attributeName === "style" || mutation.attributeName === "class") {
checkTheme();
}
}
});
observer.observe(document.documentElement, { attributes: true });
return () => {
observer.disconnect();
};
}, []);
return { theme };
}
// src/components/captcha/recaptcha-v3.tsx
var _jsxruntime = require('react/jsx-runtime');
function RecaptchaV3({ children }) {
const isHydrated = useIsHydrated();
const { captcha } = _react.useContext.call(void 0, AuthUIContext);
if ((captcha == null ? void 0 : captcha.provider) !== "google-recaptcha-v3") return children;
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
_reactrecaptchav3.GoogleReCaptchaProvider,
{
reCaptchaKey: captcha.siteKey,
useEnterprise: captcha.enterprise,
useRecaptchaNet: captcha.recaptchaNet,
children: [
isHydrated && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "style", { children: `
.grecaptcha-badge {
visibility: hidden;
border-radius: var(--radius) !important;
--tw-shadow: 0 1px 2px 0 var(--tw-shadow-color, #0000000d);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow) !important;
border-style: var(--tw-border-style) !important;
border-width: 1px;
}
.dark .grecaptcha-badge {
border-color: var(--input) !important;
}
` }),
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecaptchaV3Style, {}),
children
]
}
);
}
function RecaptchaV3Style() {
const { executeRecaptcha } = _reactrecaptchav3.useGoogleReCaptcha.call(void 0, );
const { theme } = useTheme();
const { lang } = useLang();
_react.useEffect.call(void 0, () => {
if (!executeRecaptcha) return;
const updateRecaptcha = async () => {
const iframe = document.querySelector(
"iframe[title='reCAPTCHA']"
);
if (iframe) {
const iframeSrcUrl = new URL(iframe.src);
iframeSrcUrl.searchParams.set("theme", theme);
if (lang) iframeSrcUrl.searchParams.set("hl", lang);
iframe.src = iframeSrcUrl.toString();
}
};
updateRecaptcha();
}, [executeRecaptcha, theme, lang]);
return null;
}
// src/lib/organization-refetcher.tsx
// src/hooks/use-current-organization.ts
function useCurrentOrganization({
slug: slugProp
} = {}) {
const {
organization: organizationOptions,
hooks: { useActiveOrganization, useListOrganizations }
} = _react.useContext.call(void 0, AuthUIContext);
const { pathMode, slug: contextSlug } = organizationOptions || {};
let data;
let isPending;
let isRefetching;
let refetch;
const {
data: organizations,
isPending: organizationsPending,
isRefetching: organizationsRefetching
} = useListOrganizations();
if (pathMode === "slug") {
const slug = slugProp || contextSlug;
data = organizations == null ? void 0 : organizations.find((organization) => organization.slug === slug);
isPending = organizationsPending;
isRefetching = organizationsRefetching;
} else {
const {
data: activeOrganization,
isPending: organizationPending,
isRefetching: organizationRefetching,
refetch: refetchOrganization
} = useActiveOrganization();
refetch = refetchOrganization;
data = activeOrganization;
isPending = organizationPending;
isRefetching = organizationRefetching;
}
return _react.useMemo.call(void 0,
() => ({
data,
isPending,
isRefetching,
refetch
}),
[data, isPending, isRefetching, refetch]
);
}
// src/lib/organization-refetcher.tsx
var OrganizationRefetcher = () => {
const {
hooks: { useListOrganizations, useSession },
organization: organizationOptions,
navigate,
redirectTo
} = _react.useContext.call(void 0, AuthUIContext);
const { slug, pathMode, personalPath } = organizationOptions || {};
const { data: sessionData } = useSession();
const {
data: organization,
isPending: organizationPending,
isRefetching: organizationRefetching,
refetch: refetchOrganization
} = useCurrentOrganization();
const { refetch: refetchListOrganizations } = useListOrganizations();
const { data: organizations } = useListOrganizations();
_react.useEffect.call(void 0, () => {
if (!(sessionData == null ? void 0 : sessionData.user.id)) return;
if (organization || organizations) {
refetchOrganization == null ? void 0 : refetchOrganization();
refetchListOrganizations == null ? void 0 : refetchListOrganizations();
}
}, [sessionData == null ? void 0 : sessionData.user.id]);
_react.useEffect.call(void 0, () => {
if (organizationRefetching || organizationPending) return;
if (slug && pathMode === "slug" && !organization) {
navigate(personalPath || redirectTo);
}
}, [
organization,
organizationRefetching,
organizationPending,
slug,
pathMode,
personalPath,
navigate,
redirectTo
]);
return null;
};
// src/lib/auth-ui-provider.tsx
var DefaultLink = ({ href, className, children }) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { className, href, children });
var defaultNavigate = (href) => {
window.location.href = href;
};
var defaultReplace = (href) => {
window.location.replace(href);
};
var defaultToast = ({ variant = "default", message }) => {
if (variant === "default") {
_sonner.toast.call(void 0, message);
} else {
_sonner.toast[variant](message);
}
};
var AuthUIContext = _react.createContext.call(void 0,
{}
);
var AuthUIProvider = ({
children,
authClient: authClientProp,
account: accountProp,
avatar: avatarProp,
deleteUser: deleteUserProp,
social: socialProp,
genericOAuth: genericOAuthProp,
basePath = "/auth",
baseURL = "",
captcha,
redirectTo = "/",
credentials: credentialsProp,
changeEmail = true,
freshAge = 60 * 60 * 24,
hooks: hooksProp,
mutators: mutatorsProp,
localization: localizationProp,
localizeErrors = true,
nameRequired = true,
organization: organizationProp,
teams: teamsProp,
signUp: signUpProp = true,
toast: toast2 = defaultToast,
viewPaths: viewPathsProp,
navigate,
replace,
Link = DefaultLink,
emailVerification: emailVerificationProp,
...props
}) => {
const authClient = authClientProp;
const avatar = _react.useMemo.call(void 0, () => {
if (!avatarProp) return;
if (avatarProp === true) {
return {
extension: "png",
size: 128
};
}
return {
upload: avatarProp.upload,
delete: avatarProp.delete,
extension: avatarProp.extension || "png",
size: avatarProp.size || (avatarProp.upload ? 256 : 128),
Image: avatarProp.Image
};
}, [avatarProp]);
const emailVerification = _react.useMemo.call(void 0, () => {
if (!emailVerificationProp) return;
if (emailVerificationProp === true) {
return {
otp: false
};
}
return {
otp: _nullishCoalesce(emailVerificationProp.otp, () => ( false))
};
}, [emailVerificationProp]);
const account = _react.useMemo.call(void 0, () => {
var _a;
if (accountProp === false) return;
if (accountProp === true || accountProp === void 0) {
return {
basePath: "/account",
fields: ["image", "name"],
viewPaths: _chunk2IFYYEVLcjs.accountViewPaths
};
}
const basePath2 = ((_a = accountProp.basePath) == null ? void 0 : _a.endsWith("/")) ? accountProp.basePath.slice(0, -1) : accountProp.basePath;
return {
basePath: _nullishCoalesce(basePath2, () => ( "/account")),
fields: accountProp.fields || ["image", "name"],
viewPaths: { ..._chunk2IFYYEVLcjs.accountViewPaths, ...accountProp.viewPaths }
};
}, [accountProp]);
const deleteUser = _react.useMemo.call(void 0, () => {
if (!deleteUserProp) return;
if (deleteUserProp === true) {
return {};
}
return deleteUserProp;
}, [deleteUserProp]);
const social = _react.useMemo.call(void 0, () => {
if (!socialProp) return;
return socialProp;
}, [socialProp]);
const genericOAuth = _react.useMemo.call(void 0, () => {
if (!genericOAuthProp) return;
return genericOAuthProp;
}, [genericOAuthProp]);
const credentials = _react.useMemo.call(void 0, () => {
if (credentialsProp === false) return;
if (credentialsProp === true) {
return {
forgotPassword: true,
usernameRequired: true
};
}
return {
...credentialsProp,
forgotPassword: _nullishCoalesce((credentialsProp == null ? void 0 : credentialsProp.forgotPassword), () => ( true)),
usernameRequired: _nullishCoalesce((credentialsProp == null ? void 0 : credentialsProp.usernameRequired), () => ( true))
};
}, [credentialsProp]);
const signUp = _react.useMemo.call(void 0, () => {
if (signUpProp === false) return;
if (signUpProp === true || signUpProp === void 0) {
return {
fields: ["name"]
};
}
return {
fields: signUpProp.fields || ["name"]
};
}, [signUpProp]);
const organization = _react.useMemo.call(void 0, () => {
var _a;
if (!organizationProp) return;
if (organizationProp === true) {
return {
basePath: "/organization",
viewPaths: _chunk2IFYYEVLcjs.organizationViewPaths,
customRoles: []
};
}
let logo;
if (organizationProp.logo === true) {
logo = {
extension: "png",
size: 128
};
} else if (organizationProp.logo) {
logo = {
upload: organizationProp.logo.upload,
delete: organizationProp.logo.delete,
extension: organizationProp.logo.extension || "png",
size: organizationProp.logo.size || (organizationProp.logo.upload ? 256 : 128)
};
}
const basePath2 = ((_a = organizationProp.basePath) == null ? void 0 : _a.endsWith("/")) ? organizationProp.basePath.slice(0, -1) : organizationProp.basePath;
return {
...organizationProp,
logo,
basePath: _nullishCoalesce(basePath2, () => ( "/organization")),
customRoles: organizationProp.customRoles || [],
viewPaths: {
..._chunk2IFYYEVLcjs.organizationViewPaths,
...organizationProp.viewPaths
}
};
}, [organizationProp]);
const teams = _react.useMemo.call(void 0, () => {
var _a, _b;
if (!teamsProp || !organization) return;
if (teamsProp === true) {
return {
enabled: true,
customRoles: [],
colors: {
count: 5,
prefix: "team"
}
};
}
return {
enabled: _nullishCoalesce(teamsProp.enabled, () => ( true)),
customRoles: teamsProp.customRoles || [],
colors: {
count: _nullishCoalesce(((_a = teamsProp.colors) == null ? void 0 : _a.count), () => ( 5)),
prefix: _nullishCoalesce(((_b = teamsProp.colors) == null ? void 0 : _b.prefix), () => ( "team"))
}
};
}, [teamsProp, organization]);
const defaultMutators = _react.useMemo.call(void 0, () => {
return {
deleteApiKey: (params) => authClient.apiKey.delete({
...params,
fetchOptions: { throw: true }
}),
deletePasskey: (params) => authClient.passkey.deletePasskey({
...params,
fetchOptions: { throw: true }
}),
revokeDeviceSession: (params) => authClient.multiSession.revoke({
...params,
fetchOptions: { throw: true }
}),
revokeSession: (params) => authClient.revokeSession({
...params,
fetchOptions: { throw: true }
}),
setActiveSession: (params) => authClient.multiSession.setActive({
...params,
fetchOptions: { throw: true }
}),
updateOrganization: (params) => authClient.organization.update({
...params,
fetchOptions: { throw: true }
}),
updateTeam: (params) => authClient.$fetch("/organization/update-team", {
method: "POST",
body: params,
throw: true
}),
updateUser: (params) => authClient.updateUser({
...params,
fetchOptions: { throw: true }
}),
unlinkAccount: (params) => authClient.unlinkAccount({
...params,
fetchOptions: { throw: true }
})
};
}, [authClient]);
const defaultHooks = _react.useMemo.call(void 0, () => {
return {
useSession: authClient.useSession,
useListAccounts: () => useAuthData({
queryFn: authClient.listAccounts,
cacheKey: "listAccounts"
}),
useAccountInfo: (params) => useAuthData({
queryFn: () => authClient.accountInfo(params),
cacheKey: `accountInfo:${JSON.stringify(params)}`
}),
useListDeviceSessions: () => useAuthData({
queryFn: authClient.multiSession.listDeviceSessions,
cacheKey: "listDeviceSessions"
}),
useListSessions: () => useAuthData({
queryFn: authClient.listSessions,
cacheKey: "listSessions"
}),
useListPasskeys: authClient.useListPasskeys,
useListApiKeys: () => useAuthData({
queryFn: authClient.apiKey.list,
cacheKey: "listApiKeys"
}),
useActiveOrganization: authClient.useActiveOrganization,
useListOrganizations: authClient.useListOrganizations,
useHasPermission: (params) => useAuthData({
queryFn: () => authClient.$fetch("/organization/has-permission", {
method: "POST",
body: params
}),
cacheKey: `hasPermission:${JSON.stringify(params)}`
}),
useInvitation: (params) => useAuthData({
queryFn: () => authClient.organization.getInvitation(params),
cacheKey: `invitation:${JSON.stringify(params)}`
}),
useListInvitations: (params) => useAuthData({
queryFn: () => {
var _a;
return authClient.$fetch(
`/organization/list-invitations?organizationId=${((_a = params == null ? void 0 : params.query) == null ? void 0 : _a.organizationId) || ""}`
);
},
cacheKey: `listInvitations:${JSON.stringify(params)}`
}),
useListUserInvitations: () => useAuthData({
queryFn: () => authClient.$fetch(
"/organization/list-user-invitations"
),
cacheKey: `listUserInvitations`
}),
useListMembers: (params) => useAuthData({
queryFn: () => {
var _a;
return authClient.$fetch(
`/organization/list-members?organizationId=${((_a = params == null ? void 0 : params.query) == null ? void 0 : _a.organizationId) || ""}`
);
},
cacheKey: `listMembers:${JSON.stringify(params)}`
}),
useListTeams: (params) => useAuthData({
queryFn: () => authClient.$fetch(
`/organization/list-teams?organizationId=${(params == null ? void 0 : params.organizationId) || ""}`
),
cacheKey: `listTeams:${JSON.stringify(params)}`
}),
useListTeamMembers: (params) => useAuthData({
queryFn: () => authClient.$fetch("/organization/list-team-members", {
method: "POST",
body: (params == null ? void 0 : params.teamId) ? { query: { teamId: params.teamId } } : void 0
}),
cacheKey: `listTeamMembers:${JSON.stringify(params)}`
}),
useListUserTeams: () => useAuthData({
queryFn: () => authClient.$fetch("/organization/list-user-teams"),
cacheKey: "listUserTeams"
})
};
}, [authClient]);
const viewPaths = _react.useMemo.call(void 0, () => {
return { ..._chunk2IFYYEVLcjs.authViewPaths, ...viewPathsProp };
}, [viewPathsProp]);
const localization = _react.useMemo.call(void 0, () => {
return { ..._chunk2IFYYEVLcjs.authLocalization, ...localizationProp };
}, [localizationProp]);
const hooks = _react.useMemo.call(void 0, () => {
return { ...defaultHooks, ...hooksProp };
}, [defaultHooks, hooksProp]);
const mutators = _react.useMemo.call(void 0, () => {
return { ...defaultMutators, ...mutatorsProp };
}, [defaultMutators, mutatorsProp]);
baseURL = baseURL.endsWith("/") ? baseURL.slice(0, -1) : baseURL;
basePath = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
const { data: sessionData } = hooks.useSession();
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
AuthUIContext.Provider,
{
value: {
authClient,
avatar,
basePath: basePath === "/" ? "" : basePath,
baseURL,
captcha,
redirectTo,
changeEmail,
credentials,
deleteUser,
emailVerification,
freshAge,
genericOAuth,
hooks,
mutators,
localization,
localizeErrors,
nameRequired,
organization,
teams,
account,
signUp,
social,
toast: toast2,
navigate: navigate || defaultNavigate,
replace: replace || navigate || defaultReplace,
viewPaths,
Link,
...props
},
children: [
sessionData && organization && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, OrganizationRefetcher, {}),
(captcha == null ? void 0 : captcha.provider) === "google-recaptcha-v3" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, RecaptchaV3, { children }) : children
]
}
);
};
// src/hooks/use-auth-data.ts
function useAuthData({
queryFn,
cacheKey,
staleTime = 1e4
// Default 10 seconds
}) {
var _a;
const {
hooks: { useSession },
toast: toast2,
localization,
localizeErrors
} = _react.useContext.call(void 0, AuthUIContext);
const { data: sessionData, isPending: sessionPending } = useSession();
const queryFnRef = _react.useRef.call(void 0, queryFn);
queryFnRef.current = queryFn;
const stableCacheKey = cacheKey || queryFn.toString();
const cacheEntry = _react.useSyncExternalStore.call(void 0,
_react.useCallback.call(void 0,
(callback) => authDataCache.subscribe(stableCacheKey, callback),
[stableCacheKey]
),
_react.useCallback.call(void 0,
() => authDataCache.get(stableCacheKey),
[stableCacheKey]
),
_react.useCallback.call(void 0,
() => authDataCache.get(stableCacheKey),
[stableCacheKey]
)
);
const initialized = _react.useRef.call(void 0, false);
const previousUserId = _react.useRef.call(void 0, void 0);
const [error, setError] = _react.useState.call(void 0, null);
const refetch = _react.useCallback.call(void 0, async () => {
const existingRequest = authDataCache.getInFlightRequest(stableCacheKey);
if (existingRequest) {
try {
const result = await existingRequest;
if (result.error) {
setError(result.error);
} else {
setError(null);
}
} catch (err) {
setError(err);
}
return;
}
if ((cacheEntry == null ? void 0 : cacheEntry.data) !== void 0) {
authDataCache.setRefetching(stableCacheKey, true);
}
const fetchPromise = queryFnRef.current();
authDataCache.setInFlightRequest(stableCacheKey, fetchPromise);
try {
const { data, error: error2 } = await fetchPromise;
if (error2) {
setError(error2);
toast2({
variant: "error",
message: _chunk2IFYYEVLcjs.getLocalizedError.call(void 0, {
error: error2,
localization,
localizeErrors
})
});
} else {
setError(null);
}
authDataCache.set(stableCacheKey, data);
} catch (err) {
const error2 = err;
setError(error2);
toast2({
variant: "error",
message: _chunk2IFYYEVLcjs.getLocalizedError.call(void 0, {
error: error2,
localization,
localizeErrors
})
});
} finally {
authDataCache.setRefetching(stableCacheKey, false);
authDataCache.removeInFlightRequest(stableCacheKey);
}
}, [stableCacheKey, toast2, localization, localizeErrors, cacheEntry]);
_react.useEffect.call(void 0, () => {
var _a2;
const currentUserId = (_a2 = sessionData == null ? void 0 : sessionData.user) == null ? void 0 : _a2.id;
if (!sessionData) {
authDataCache.setRefetching(stableCacheKey, false);
authDataCache.clear(stableCacheKey);
initialized.current = false;
previousUserId.current = void 0;
return;
}
const userIdChanged = previousUserId.current !== void 0 && previousUserId.current !== currentUserId;
if (userIdChanged) {
authDataCache.clear(stableCacheKey);
}
const hasCachedData = (cacheEntry == null ? void 0 : cacheEntry.data) !== void 0;
const isStale = !cacheEntry || Date.now() - cacheEntry.timestamp > staleTime;
if (!initialized.current || !hasCachedData || userIdChanged || hasCachedData && isStale) {
if (!hasCachedData || isStale) {
initialized.current = true;
refetch();
}
}
previousUserId.current = currentUserId;
}, [
sessionData,
(_a = sessionData == null ? void 0 : sessionData.user) == null ? void 0 : _a.id,
stableCacheKey,
refetch,
cacheEntry,
staleTime
]);
const isPending = sessionPending || (cacheEntry == null ? void 0 : cacheEntry.data) === void 0 && !error;
return {
data: _nullishCoalesce((cacheEntry == null ? void 0 : cacheEntry.data), () => ( null)),
isPending,
isRefetching: _nullishCoalesce((cacheEntry == null ? void 0 : cacheEntry.isRefetching), () => ( false)),
error,
refetch
};
}
exports.useIsHydrated = useIsHydrated; exports.useLang = useLang; exports.useTheme = useTheme; exports.useAuthData = useAuthData; exports.useCurrentOrganization = useCurrentOrganization; exports.AuthUIContext = AuthUIContext; exports.AuthUIProvider = AuthUIProvider;