feat: add infinite scroll & fix some ui
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { useWAClient } from "@/app/context";
|
||||
import { getId } from "@/lib/utils";
|
||||
import type { WhatsAppClient } from "@client/whatsapp";
|
||||
import { useQuery, type UseQueryOptions } from "@tanstack/react-query";
|
||||
|
||||
async function getChats(client: WhatsAppClient) {
|
||||
return client
|
||||
.listChats({ limit: 500 })
|
||||
.then((res) => res.map((i) => ({ ...i, id: getId(i.id) })));
|
||||
}
|
||||
|
||||
type Response = Awaited<ReturnType<typeof getChats>>;
|
||||
|
||||
export function useChats(opt?: Partial<UseQueryOptions>) {
|
||||
const { client, isReady } = useWAClient();
|
||||
return useQuery<Response, Error>({
|
||||
queryKey: ["chats"],
|
||||
queryFn: () => getChats(client),
|
||||
enabled: isReady,
|
||||
...((opt || {}) as any),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
type HashLocation = {
|
||||
path: string;
|
||||
query: URLSearchParams;
|
||||
search: string;
|
||||
};
|
||||
|
||||
type HashRouter = HashLocation & {
|
||||
navigate: (path: string, options?: NavigateOptions) => void;
|
||||
push: (path: string) => void;
|
||||
reset: (path?: string) => void;
|
||||
canGoback: () => boolean;
|
||||
goBack: () => void;
|
||||
};
|
||||
|
||||
type NavigateOptions = {
|
||||
replace?: boolean;
|
||||
};
|
||||
|
||||
function getHashLocation(): HashLocation {
|
||||
const hash = window.location.hash.slice(1);
|
||||
|
||||
const [pathname, search = ""] = hash.split("?");
|
||||
|
||||
const path = pathname
|
||||
? pathname.startsWith("/")
|
||||
? pathname
|
||||
: `/${pathname}`
|
||||
: "/";
|
||||
|
||||
return {
|
||||
path,
|
||||
search: search ? `?${search}` : "",
|
||||
query: new URLSearchParams(search),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePath(path: string) {
|
||||
return path.startsWith("/") ? path : `/${path}`;
|
||||
}
|
||||
|
||||
function setHashPath(path: string, replace = false) {
|
||||
const normalized = normalizePath(path);
|
||||
const hash = `#${normalized}`;
|
||||
|
||||
if (replace) {
|
||||
window.history.replaceState(null, "", hash);
|
||||
|
||||
// replaceState doesn't emit hashchange
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
} else {
|
||||
window.location.hash = normalized;
|
||||
}
|
||||
}
|
||||
|
||||
export function useHashRouter(): HashRouter {
|
||||
const [location, setLocation] = useState<HashLocation>(getHashLocation);
|
||||
|
||||
useEffect(() => {
|
||||
const handleChange = () => {
|
||||
setLocation(getHashLocation());
|
||||
};
|
||||
|
||||
window.addEventListener("hashchange", handleChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("hashchange", handleChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const navigate = useCallback(
|
||||
(path: string, options: NavigateOptions = {}) => {
|
||||
setHashPath(path, options.replace);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const push = useCallback(
|
||||
(path: string) => {
|
||||
navigate(path);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const reset = useCallback(
|
||||
(path = "/") => {
|
||||
navigate(path, { replace: true });
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const canGoback = useCallback(() => window.history.length > 1, []);
|
||||
|
||||
const goBack = useCallback(() => {
|
||||
if (canGoback()) {
|
||||
window.history.back();
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
}, [canGoback, reset]);
|
||||
|
||||
return {
|
||||
...location,
|
||||
navigate,
|
||||
push,
|
||||
reset,
|
||||
canGoback,
|
||||
goBack,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export function useIntersection(
|
||||
callback: (entry: IntersectionObserverEntry) => void,
|
||||
options: any = {},
|
||||
) {
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (!element) return;
|
||||
|
||||
const observer = new IntersectionObserver(([entry]) => {
|
||||
if (entry.isIntersecting) {
|
||||
callback(entry);
|
||||
}
|
||||
}, options);
|
||||
|
||||
observer.observe(element);
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [callback, options]);
|
||||
|
||||
return ref;
|
||||
}
|
||||
Reference in New Issue
Block a user