first commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient } from "@calcom/prisma/client";
|
||||
|
||||
export type ManagedUser = {
|
||||
id: number;
|
||||
email: string;
|
||||
username: string | null;
|
||||
timeZone: string;
|
||||
weekStart: string;
|
||||
createdDate: Date;
|
||||
timeFormat: number | null;
|
||||
defaultScheduleId: number | null;
|
||||
};
|
||||
|
||||
export const useOAuthClients = () => {
|
||||
const query = useQuery<ApiSuccessResponse<PlatformOAuthClient[]>>({
|
||||
queryKey: ["oauth-clients"],
|
||||
queryFn: () => {
|
||||
return fetch("/api/v2/oauth-clients", {
|
||||
method: "get",
|
||||
headers: { "Content-type": "application/json" },
|
||||
}).then((res) => res.json());
|
||||
},
|
||||
});
|
||||
|
||||
return { ...query, data: query.data?.data ?? [] };
|
||||
};
|
||||
|
||||
export const useOAuthClient = (clientId?: string) => {
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
data: response,
|
||||
isFetched,
|
||||
isError,
|
||||
isFetching,
|
||||
isSuccess,
|
||||
isFetchedAfterMount,
|
||||
refetch,
|
||||
} = useQuery<ApiSuccessResponse<PlatformOAuthClient>>({
|
||||
queryKey: ["oauth-client", clientId],
|
||||
queryFn: () => {
|
||||
return fetch(`/api/v2/oauth-clients/${clientId}`, {
|
||||
method: "get",
|
||||
headers: { "Content-type": "application/json" },
|
||||
}).then((res) => res.json());
|
||||
},
|
||||
enabled: Boolean(clientId),
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
error,
|
||||
data: response?.data,
|
||||
isFetched,
|
||||
isError,
|
||||
isFetching,
|
||||
isSuccess,
|
||||
isFetchedAfterMount,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
export const useGetOAuthClientManagedUsers = (clientId: string) => {
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
data: response,
|
||||
refetch,
|
||||
} = useQuery<ApiSuccessResponse<ManagedUser[]>>({
|
||||
queryKey: ["oauth-client-managed-users", clientId],
|
||||
queryFn: () => {
|
||||
return fetch(`/api/v2/oauth-clients/${clientId}/managed-users`, {
|
||||
method: "get",
|
||||
headers: { "Content-type": "application/json" },
|
||||
}).then((res) => res.json());
|
||||
},
|
||||
enabled: Boolean(clientId),
|
||||
});
|
||||
|
||||
return { isLoading, error, data: response?.data, refetch };
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type {
|
||||
ApiResponse,
|
||||
CreateOAuthClientInput,
|
||||
DeleteOAuthClientInput,
|
||||
SubscribeTeamInput,
|
||||
} from "@calcom/platform-types";
|
||||
import type { OAuthClient } from "@calcom/prisma/client";
|
||||
|
||||
interface IPersistOAuthClient {
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
}
|
||||
|
||||
export const useCreateOAuthClient = (
|
||||
{ onSuccess, onError }: IPersistOAuthClient = {
|
||||
onSuccess: () => {
|
||||
return;
|
||||
},
|
||||
onError: () => {
|
||||
return;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
return useMutation<
|
||||
ApiResponse<{ clientId: string; clientSecret: string }>,
|
||||
unknown,
|
||||
CreateOAuthClientInput
|
||||
>({
|
||||
mutationFn: (data) => {
|
||||
return fetch("/api/v2/oauth-clients", {
|
||||
method: "post",
|
||||
headers: { "Content-type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res.json());
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data.status === SUCCESS_STATUS) {
|
||||
onSuccess?.();
|
||||
} else {
|
||||
onError?.();
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
onError?.();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateOAuthClient = (
|
||||
{ onSuccess, onError, clientId }: IPersistOAuthClient & { clientId?: string } = {
|
||||
onSuccess: () => {
|
||||
return;
|
||||
},
|
||||
onError: () => {
|
||||
return;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
const mutation = useMutation<
|
||||
ApiResponse<{ clientId: string; clientSecret: string }>,
|
||||
unknown,
|
||||
Omit<CreateOAuthClientInput, "permissions">
|
||||
>({
|
||||
mutationFn: (data) => {
|
||||
return fetch(`/api/v2/oauth-clients/${clientId}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res?.json());
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data.status === SUCCESS_STATUS) {
|
||||
onSuccess?.();
|
||||
} else {
|
||||
onError?.();
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
onError?.();
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
||||
export const useDeleteOAuthClient = (
|
||||
{ onSuccess, onError }: IPersistOAuthClient = {
|
||||
onSuccess: () => {
|
||||
return;
|
||||
},
|
||||
onError: () => {
|
||||
return;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
const mutation = useMutation<ApiResponse<OAuthClient>, unknown, DeleteOAuthClientInput>({
|
||||
mutationFn: (data) => {
|
||||
const { id } = data;
|
||||
return fetch(`/api/v2/oauth-clients/${id}`, {
|
||||
method: "delete",
|
||||
headers: { "Content-type": "application/json" },
|
||||
}).then((res) => res?.json());
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data.status === SUCCESS_STATUS) {
|
||||
onSuccess?.();
|
||||
} else {
|
||||
onError?.();
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
onError?.();
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
||||
export const useCheckTeamBilling = (teamId?: number | null, isPlatformTeam?: boolean | null) => {
|
||||
const QUERY_KEY = "check-team-billing";
|
||||
const isTeamBilledAlready = useQuery({
|
||||
queryKey: [QUERY_KEY, teamId],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`/api/v2/billing/${teamId}/check`, {
|
||||
method: "get",
|
||||
headers: { "Content-type": "application/json" },
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
return data.data;
|
||||
},
|
||||
enabled: !!teamId && !!isPlatformTeam,
|
||||
});
|
||||
|
||||
return isTeamBilledAlready;
|
||||
};
|
||||
|
||||
export const useSubscribeTeamToStripe = (
|
||||
{
|
||||
onSuccess,
|
||||
onError,
|
||||
teamId,
|
||||
}: { teamId?: number | null; onSuccess: (redirectUrl: string) => void; onError: () => void } = {
|
||||
onSuccess: () => {
|
||||
return;
|
||||
},
|
||||
onError: () => {
|
||||
return;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
const mutation = useMutation<ApiResponse<{ action: string; url: string }>, unknown, SubscribeTeamInput>({
|
||||
mutationFn: (data) => {
|
||||
return fetch(`/api/v2/billing/${teamId}/subscribe`, {
|
||||
method: "post",
|
||||
headers: { "Content-type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => res?.json());
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (data.status === SUCCESS_STATUS) {
|
||||
onSuccess?.(data.data?.url);
|
||||
} else {
|
||||
onError?.();
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
onError?.();
|
||||
},
|
||||
});
|
||||
|
||||
return mutation;
|
||||
};
|
||||
58
calcom/apps/web/lib/hooks/useAppsData.ts
Normal file
58
calcom/apps/web/lib/hooks/useAppsData.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { FormValues } from "pages/event-types/[type]";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import type { GetAppData, SetAppData } from "@calcom/app-store/EventTypeAppContext";
|
||||
import type { EventTypeAppsList } from "@calcom/app-store/utils";
|
||||
|
||||
const useAppsData = () => {
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const allAppsData = formMethods.watch("metadata")?.apps || {};
|
||||
|
||||
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
||||
formMethods.setValue(
|
||||
"metadata",
|
||||
{
|
||||
...formMethods.getValues("metadata"),
|
||||
apps: _allAppsData,
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
};
|
||||
|
||||
const getAppDataGetter = (appId: EventTypeAppsList): GetAppData => {
|
||||
return function (key) {
|
||||
const appData = allAppsData[appId as keyof typeof allAppsData] || {};
|
||||
if (key) {
|
||||
return appData[key as keyof typeof appData];
|
||||
}
|
||||
return appData;
|
||||
};
|
||||
};
|
||||
|
||||
const eventTypeFormMetadata = formMethods.getValues("metadata");
|
||||
|
||||
const getAppDataSetter = (
|
||||
appId: EventTypeAppsList,
|
||||
appCategories: string[],
|
||||
credentialId?: number
|
||||
): SetAppData => {
|
||||
return function (key, value) {
|
||||
// Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render)
|
||||
const allAppsDataFromForm = formMethods.getValues("metadata")?.apps || {};
|
||||
const appData = allAppsDataFromForm[appId];
|
||||
setAllAppsData({
|
||||
...allAppsDataFromForm,
|
||||
[appId]: {
|
||||
...appData,
|
||||
[key]: value,
|
||||
credentialId,
|
||||
appCategories,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return { getAppDataGetter, getAppDataSetter, eventTypeFormMetadata };
|
||||
};
|
||||
|
||||
export default useAppsData;
|
||||
9
calcom/apps/web/lib/hooks/useCurrentUserId.ts
Normal file
9
calcom/apps/web/lib/hooks/useCurrentUserId.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import useMeQuery from "./useMeQuery";
|
||||
|
||||
export const useCurrentUserId = () => {
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
return user?.id;
|
||||
};
|
||||
|
||||
export default useCurrentUserId;
|
||||
43
calcom/apps/web/lib/hooks/useFileReader.ts
Normal file
43
calcom/apps/web/lib/hooks/useFileReader.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
type ReadAsMethod = "readAsText" | "readAsDataURL" | "readAsArrayBuffer" | "readAsBinaryString";
|
||||
|
||||
type UseFileReaderProps = {
|
||||
method: ReadAsMethod;
|
||||
onLoad?: (result: unknown) => void;
|
||||
};
|
||||
|
||||
export const useFileReader = (options: UseFileReaderProps) => {
|
||||
const { method = "readAsText", onLoad } = options;
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<DOMException | null>(null);
|
||||
const [result, setResult] = useState<string | ArrayBuffer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!file && result) {
|
||||
setResult(null);
|
||||
}
|
||||
}, [file, result]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadstart = () => setLoading(true);
|
||||
reader.onloadend = () => setLoading(false);
|
||||
reader.onerror = () => setError(reader.error);
|
||||
|
||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||
setResult(e.target?.result ?? null);
|
||||
if (onLoad) {
|
||||
onLoad(e.target?.result ?? null);
|
||||
}
|
||||
};
|
||||
reader[method](file);
|
||||
}, [file, method, onLoad]);
|
||||
|
||||
return [{ result, error, file, loading }, setFile] as const;
|
||||
};
|
||||
42
calcom/apps/web/lib/hooks/useInViewObserver.ts
Normal file
42
calcom/apps/web/lib/hooks/useInViewObserver.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
|
||||
const isInteractionObserverSupported = typeof window !== "undefined" && "IntersectionObserver" in window;
|
||||
|
||||
export const useInViewObserver = (onInViewCallback: () => void) => {
|
||||
const [node, setRef] = React.useState<HTMLElement | null>(null);
|
||||
|
||||
const onInViewCallbackRef = React.useRef(onInViewCallback);
|
||||
onInViewCallbackRef.current = onInViewCallback;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isInteractionObserverSupported) {
|
||||
// Skip interaction check if not supported in browser
|
||||
return;
|
||||
}
|
||||
|
||||
let observer: IntersectionObserver;
|
||||
if (node && node.parentElement) {
|
||||
observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
onInViewCallbackRef.current();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: document.body,
|
||||
}
|
||||
);
|
||||
observer.observe(node);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
};
|
||||
}, [node]);
|
||||
|
||||
return {
|
||||
ref: setRef,
|
||||
};
|
||||
};
|
||||
14
calcom/apps/web/lib/hooks/useIsBookingPage.ts
Normal file
14
calcom/apps/web/lib/hooks/useIsBookingPage.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
|
||||
export default function useIsBookingPage(): boolean {
|
||||
const pathname = usePathname();
|
||||
const isBookingPage = ["/booking/", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route));
|
||||
|
||||
const searchParams = useCompatSearchParams();
|
||||
const userParam = Boolean(searchParams?.get("user"));
|
||||
const teamParam = Boolean(searchParams?.get("team"));
|
||||
|
||||
return isBookingPage || userParam || teamParam;
|
||||
}
|
||||
13
calcom/apps/web/lib/hooks/useMeQuery.ts
Normal file
13
calcom/apps/web/lib/hooks/useMeQuery.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
export function useMeQuery() {
|
||||
const meQuery = trpc.viewer.me.useQuery(undefined, {
|
||||
retry(failureCount) {
|
||||
return failureCount > 3;
|
||||
},
|
||||
});
|
||||
|
||||
return meQuery;
|
||||
}
|
||||
|
||||
export default useMeQuery;
|
||||
20
calcom/apps/web/lib/hooks/useMediaQuery.ts
Normal file
20
calcom/apps/web/lib/hooks/useMediaQuery.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// lets refactor and move this into packages/lib/hooks/
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const useMediaQuery = (query: string) => {
|
||||
const [matches, setMatches] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const media = window.matchMedia(query);
|
||||
if (media.matches !== matches) {
|
||||
setMatches(media.matches);
|
||||
}
|
||||
const listener = () => setMatches(media.matches);
|
||||
window.addEventListener("resize", listener);
|
||||
return () => window.removeEventListener("resize", listener);
|
||||
}, [matches, query]);
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
export default useMediaQuery;
|
||||
28
calcom/apps/web/lib/hooks/useRouterQuery.ts
Normal file
28
calcom/apps/web/lib/hooks/useRouterQuery.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
|
||||
export default function useRouterQuery<T extends string>(name: T) {
|
||||
const searchParams = useCompatSearchParams();
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const setQuery = useCallback(
|
||||
(newValue: string | number | null | undefined) => {
|
||||
const _searchParams = new URLSearchParams(searchParams ?? undefined);
|
||||
if (typeof newValue === "undefined") {
|
||||
// when newValue is of type undefined, clear the search param.
|
||||
_searchParams.delete(name);
|
||||
} else {
|
||||
_searchParams.set(name, newValue as string);
|
||||
}
|
||||
router.replace(`${pathname}?${_searchParams.toString()}`);
|
||||
},
|
||||
[name, pathname, router, searchParams]
|
||||
);
|
||||
|
||||
return { [name]: searchParams?.get(name), setQuery } as {
|
||||
[K in T]: string | undefined;
|
||||
} & { setQuery: typeof setQuery };
|
||||
}
|
||||
9
calcom/apps/web/lib/hooks/useToggleQuery.tsx
Normal file
9
calcom/apps/web/lib/hooks/useToggleQuery.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
|
||||
export function useToggleQuery(name: string) {
|
||||
const searchParams = useCompatSearchParams();
|
||||
|
||||
return {
|
||||
isOn: searchParams?.get(name) === "1",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user