diff --git a/src/routes/notifications.tsx b/src/routes/notifications.tsx
index 4fd5c3d..eefd301 100644
--- a/src/routes/notifications.tsx
+++ b/src/routes/notifications.tsx
@@ -19,6 +19,7 @@ import {
import { useAuth } from "~/providers/UnifiedAuthProvider";
import {
constellationURLAtom,
+ enableBitesAtom,
imgCDNAtom,
postInteractionsFiltersAtom,
} from "~/utils/atoms";
@@ -56,6 +57,7 @@ export const Route = createFileRoute("/notifications")({
});
export default function NotificationsTabs() {
+ const [bitesEnabled] = useAtom(enableBitesAtom);
return (
,
Follows: ,
"Post Interactions": ,
+ ...bitesEnabled ? {
+ Bites: ,
+ } : {}
}}
/>
);
@@ -200,6 +205,76 @@ export function FollowsTab({did}:{did?:string}) {
);
}
+
+export function BitesTab({did}:{did?:string}) {
+ const { agent } = useAuth();
+ const userdidunsafe = did ?? agent?.did;
+ const { data: identity} = useQueryIdentity(userdidunsafe);
+ const userdid = identity?.did;
+
+ const [constellationurl] = useAtom(constellationURLAtom);
+ const infinitequeryresults = useInfiniteQuery({
+ ...yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks(
+ {
+ constellation: constellationurl,
+ method: "/links",
+ target: "at://"+userdid,
+ collection: "net.wafrn.feed.bite",
+ path: ".subject",
+ staleMult: 0 // safe fun
+ }
+ ),
+ enabled: !!userdid,
+ });
+
+ const {
+ data: infiniteFollowsData,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ isLoading,
+ isError,
+ error,
+ } = infinitequeryresults;
+
+ const followsAturis = React.useMemo(() => {
+ // Get all replies from the standard infinite query
+ return (
+ infiniteFollowsData?.pages.flatMap(
+ (page) =>
+ page?.linking_records.map(
+ (r) => `at://${r.did}/${r.collection}/${r.rkey}`
+ ) ?? []
+ ) ?? []
+ );
+ }, [infiniteFollowsData]);
+
+ useReusableTabScrollRestore("Notifications");
+
+ if (isLoading) return ;
+ if (isError) return ;
+
+ if (!followsAturis?.length) return ;
+
+ return (
+ <>
+ {followsAturis.map((m) => (
+
+ ))}
+
+ {hasNextPage && (
+
+ )}
+ >
+ );
+}
+
function PostInteractionsTab() {
const { agent } = useAuth();
const { data: identity } = useQueryIdentity(agent?.did);
@@ -499,6 +574,7 @@ function InteractionsButton({
export function NotificationItem({ notification }: { notification: string }) {
const aturi = new AtUri(notification);
+ const bite = aturi.collection === "net.wafrn.feed.bite";
const navigate = useNavigate();
const { data: identity } = useQueryIdentity(aturi.host);
const resolvedDid = identity?.did;
diff --git a/src/routes/profile.$did/index.tsx b/src/routes/profile.$did/index.tsx
index 3d6e0ed..705b99f 100644
--- a/src/routes/profile.$did/index.tsx
+++ b/src/routes/profile.$did/index.tsx
@@ -1,5 +1,6 @@
-import { RichText } from "@atproto/api";
+import { Agent, RichText } from "@atproto/api";
import * as ATPAPI from "@atproto/api";
+import { TID } from "@atproto/common-web";
import { useQueryClient } from "@tanstack/react-query";
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
import { useAtom } from "jotai";
@@ -16,7 +17,7 @@ import {
UniversalPostRendererATURILoader,
} from "~/components/UniversalPostRenderer";
import { useAuth } from "~/providers/UnifiedAuthProvider";
-import { imgCDNAtom, profileChipsAtom } from "~/utils/atoms";
+import { enableBitesAtom, imgCDNAtom, profileChipsAtom } from "~/utils/atoms";
import {
toggleFollow,
useGetFollowState,
@@ -143,6 +144,7 @@ function ProfileComponent() {
+
{/*
todo: full follow and unfollow backfill (along with partial likes backfill,
just enough for it to be useful)
@@ -811,6 +813,60 @@ export function FollowButton({
);
}
+export function BiteButton({
+ targetdidorhandle,
+}: {
+ targetdidorhandle: string;
+}) {
+ const { agent } = useAuth();
+ const { data: identity } = useQueryIdentity(targetdidorhandle);
+ const [show] = useAtom(enableBitesAtom);
+
+ if (!show) return
+
+ return (
+ <>
+
+ >
+ );
+}
+
+function sendBite({
+ agent,
+ targetDid,
+}: {
+ agent?: Agent;
+ targetDid?: string;
+}) {
+ if (!agent?.did || !targetDid) return;
+ const newRecord = {
+ repo: agent.did,
+ collection: "net.wafrn.feed.bite",
+ rkey: TID.next().toString(),
+ record: {
+ $type: "net.wafrn.feed.bite",
+ subject: "at://"+targetDid,
+ createdAt: new Date().toISOString(),
+ },
+ };
+
+ agent.com.atproto.repo.createRecord(newRecord).catch((err) => {
+ console.error("Bite failed:", err);
+ });
+}
+
+
export function Mutual({ targetdidorhandle }: { targetdidorhandle: string }) {
const { agent } = useAuth();
const { data: identity } = useQueryIdentity(targetdidorhandle);
diff --git a/src/routes/settings.tsx b/src/routes/settings.tsx
index f6552d9..f9becfd 100644
--- a/src/routes/settings.tsx
+++ b/src/routes/settings.tsx
@@ -1,6 +1,7 @@
import { createFileRoute } from "@tanstack/react-router";
-import { useAtom } from "jotai";
-import { Slider } from "radix-ui";
+import { useAtom, useAtomValue, useSetAtom } from "jotai";
+import { Slider, Switch } from "radix-ui";
+import { useEffect,useState } from "react";
import { Header } from "~/components/Header";
import Login from "~/components/Login";
@@ -11,6 +12,7 @@ import {
defaultImgCDN,
defaultslingshotURL,
defaultVideoCDN,
+ enableBitesAtom,
hueAtom,
imgCDNAtom,
slingshotURLAtom,
@@ -68,12 +70,63 @@ export function Settings() {
/>
+
please restart/refresh the app if changes arent applying correctly
>
);
}
+
+export function SwitchSetting({
+ atom,
+ title,
+ description,
+}: {
+ atom: typeof enableBitesAtom;
+ title?: string;
+ description?: string;
+}) {
+ const value = useAtomValue(atom);
+ const setValue = useSetAtom(atom);
+
+ const [hydrated, setHydrated] = useState(false);
+ // eslint-disable-next-line react-hooks/set-state-in-effect
+ useEffect(() => setHydrated(true), []);
+
+ if (!hydrated) {
+ // Avoid rendering Switch until we know storage is loaded
+ return null;
+ }
+
+ return (
+
+
+
+ {description}
+
+
+
setValue(v)}
+ className="w-10 h-6 bg-gray-300 rounded-full relative data-[state=checked]:bg-blue-500 transition-colors"
+ >
+
+
+
+ );
+}
+
function Hue() {
const [hue, setHue] = useAtom(hueAtom);
return (
diff --git a/src/utils/atoms.ts b/src/utils/atoms.ts
index 470a30d..e831180 100644
--- a/src/utils/atoms.ts
+++ b/src/utils/atoms.ts
@@ -128,3 +128,12 @@ hueAtom.onMount = (setAtom) => {
// console.log("atom get ", initial);
// document.documentElement.style.setProperty(cssVar, initial.toString());
// }
+
+
+
+// fun stuff
+
+export const enableBitesAtom = atomWithStorage
(
+ "enableBitesAtom",
+ false
+);
diff --git a/src/utils/useQuery.ts b/src/utils/useQuery.ts
index 6991db5..0d3d10a 100644
--- a/src/utils/useQuery.ts
+++ b/src/utils/useQuery.ts
@@ -654,8 +654,10 @@ export function yknowIReallyHateThisButWhateverGuardedConstructConstellationInfi
method: '/links'
target?: string
collection: string
- path: string
+ path: string,
+ staleMult?: number
}) {
+ const safemult = query?.staleMult || 1;
// console.log(
// 'yknowIReallyHateThisButWhateverGuardedConstructConstellationInfiniteQueryLinks',
// query,
@@ -697,7 +699,7 @@ export function yknowIReallyHateThisButWhateverGuardedConstructConstellationInfi
return (lastPage as any)?.cursor ?? undefined
},
initialPageParam: undefined,
- staleTime: 5 * 60 * 1000,
- gcTime: 5 * 60 * 1000,
+ staleTime: 5 * 60 * 1000 * safemult,
+ gcTime: 5 * 60 * 1000 * safemult,
})
}
\ No newline at end of file