Skip to content

Commit

Permalink
fix(discover): update follow status after add feed, closes #269, closes
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Sep 20, 2024
1 parent 5e34d36 commit 32a55ec
Showing 1 changed file with 154 additions and 91 deletions.
245 changes: 154 additions & 91 deletions apps/renderer/src/modules/discover/form.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useMutation } from "@tanstack/react-query"
import { useEffect } from "react"
import { useSingleton } from "foxact/use-singleton"
import { produce } from "immer"
import { atom, useAtomValue, useStore } from "jotai"
import type { FC } from "react"
import { memo, useCallback, useEffect } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { z } from "zod"
Expand Down Expand Up @@ -55,6 +59,7 @@ const info: Record<
},
}

type DiscoverSearchData = Awaited<ReturnType<typeof apiClient.discover.$post>>["data"]
export function DiscoverForm({ type }: { type: string }) {
const { prefix, default: defaultValue } = info[type]
const form = useForm<z.infer<typeof formSchema>>({
Expand All @@ -64,6 +69,8 @@ export function DiscoverForm({ type }: { type: string }) {
},
})
const { t } = useTranslation()

const jotaiStore = useStore()
const mutation = useMutation({
mutationFn: async (keyword: string) => {
const { data } = await apiClient.discover.$post({
Expand All @@ -72,9 +79,14 @@ export function DiscoverForm({ type }: { type: string }) {
},
})

jotaiStore.set(discoverSearchDataAtom, data)

return data
},
})
const discoverSearchDataAtom = useSingleton(() => atom<DiscoverSearchData>()).current

const discoverSearchData = useAtomValue(discoverSearchDataAtom)

const { present, dismissAll } = useModalStack()

Expand Down Expand Up @@ -120,6 +132,41 @@ export function DiscoverForm({ type }: { type: string }) {
form.setValue("keyword", trimmedKeyword)
}, [form, keyword, prefix])

const handleSuccess = useCallback(
(item: DiscoverSearchData[number]) => {
const currentData = jotaiStore.get(discoverSearchDataAtom)
if (!currentData) return
jotaiStore.set(
discoverSearchDataAtom,
produce(currentData, (draft) => {
const sub = draft.find((i) => i.feed.id === item.feed.id)
if (!sub) return
sub.isSubscribed = true
sub.subscriptionCount = -~(sub.subscriptionCount as number)
}),
)
},
[discoverSearchDataAtom, jotaiStore],
)

const handleUnSubscribed = useCallback(
(item: DiscoverSearchData[number]) => {
const currentData = jotaiStore.get(discoverSearchDataAtom)
if (!currentData) return
jotaiStore.set(
discoverSearchDataAtom,
produce(currentData, (draft) => {
const sub = draft.find((i) => i.feed.id === item.feed.id)
if (!sub) return
sub.isSubscribed = false
sub.subscriptionCount = Number.isNaN(sub.subscriptionCount)
? 0
: (sub.subscriptionCount as number) - 1
}),
)
},
[discoverSearchDataAtom, jotaiStore],
)
return (
<>
<Form {...form}>
Expand Down Expand Up @@ -151,100 +198,116 @@ export function DiscoverForm({ type }: { type: string }) {
{mutation.data?.length > 1 && "s"}
</div>
<div className="space-y-6 text-sm">
{mutation.data.map((item) => (
<Card
data-feed-id={item.feed.id}
key={item.feed.url || item.docs}
className="select-text"
>
<CardHeader>
<FollowSummary className="max-w-[462px]" feed={item.feed} docs={item.docs} />
</CardHeader>
{item.docs ? (
<CardFooter>
<a href={item.docs} target="_blank" rel="noreferrer">
<Button>View Docs</Button>
</a>
</CardFooter>
) : (
<>
<CardContent>
{!!item.entries?.length && (
<div className="grid grid-cols-4 gap-4">
{item.entries
.filter((e) => !!e)
.map((entry) => {
const assertEntry = entry
return (
<a
key={assertEntry.id}
href={assertEntry.url || void 0}
target="_blank"
className="flex min-w-0 flex-1 flex-col items-center gap-1"
rel="noreferrer"
>
{assertEntry.media?.[0] ? (
<Media
src={assertEntry.media?.[0].url}
type={assertEntry.media?.[0].type}
previewImageUrl={assertEntry.media?.[0].preview_image_url}
className="aspect-square w-full"
/>
) : (
<div className="flex aspect-square w-full overflow-hidden rounded bg-stone-100 p-2 text-xs leading-tight text-zinc-500">
{assertEntry.title}
</div>
)}
<div className="line-clamp-2 w-full text-xs leading-tight">
{assertEntry.title}
</div>
</a>
)
})}
</div>
)}
</CardContent>
<CardFooter>
{item.isSubscribed ? (
<Button variant="outline" disabled>
Followed
</Button>
) : (
<Button
onClick={() => {
present({
title: "Add Feed",
content: ({ dismiss }) => (
<FeedForm
asWidget
url={item.feed.url}
id={item.feed.id}
defaultValues={{
view: getSidebarActiveView().toString(),
}}
onSuccess={dismiss}
/>
),
})
}}
>
Follow
</Button>
)}
<div className="ml-6 text-zinc-500">
<span className="font-medium text-zinc-800 dark:text-zinc-200">
{item.subscriptionCount}
</span>{" "}
Followers
</div>
</CardFooter>
</>
)}
</Card>
{discoverSearchData?.map((item) => (
<SearchCard
key={item.feed.id}
item={item}
onSuccess={handleSuccess}
onUnSubscribed={handleUnSubscribed}
/>
))}
</div>
</div>
)}
</>
)
}

const SearchCard: FC<{
item: DiscoverSearchData[number]
onSuccess: (item: DiscoverSearchData[number]) => void
onUnSubscribed?: (item: DiscoverSearchData[number]) => void
}> = memo(({ item, onSuccess }) => {
const { present } = useModalStack()

return (
<Card data-feed-id={item.feed.id} key={item.feed.url || item.docs} className="select-text">
<CardHeader>
<FollowSummary className="max-w-[462px]" feed={item.feed} docs={item.docs} />
</CardHeader>
{item.docs ? (
<CardFooter>
<a href={item.docs} target="_blank" rel="noreferrer">
<Button>View Docs</Button>
</a>
</CardFooter>
) : (
<>
<CardContent>
{!!item.entries?.length && (
<div className="grid grid-cols-4 gap-4">
{item.entries
.filter((e) => !!e)
.map((entry) => {
const assertEntry = entry
return (
<a
key={assertEntry.id}
href={assertEntry.url || void 0}
target="_blank"
className="flex min-w-0 flex-1 flex-col items-center gap-1"
rel="noreferrer"
>
{assertEntry.media?.[0] ? (
<Media
src={assertEntry.media?.[0].url}
type={assertEntry.media?.[0].type}
previewImageUrl={assertEntry.media?.[0].preview_image_url}
className="aspect-square w-full"
/>
) : (
<div className="flex aspect-square w-full overflow-hidden rounded bg-stone-100 p-2 text-xs leading-tight text-zinc-500">
{assertEntry.title}
</div>
)}
<div className="line-clamp-2 w-full text-xs leading-tight">
{assertEntry.title}
</div>
</a>
)
})}
</div>
)}
</CardContent>
<CardFooter>
{item.isSubscribed ? (
<Button variant="outline" disabled>
Followed
</Button>
) : (
<Button
onClick={() => {
present({
title: "Add Feed",
content: ({ dismiss }) => (
<FeedForm
asWidget
url={item.feed.url}
id={item.feed.id}
defaultValues={{
view: getSidebarActiveView().toString(),
}}
onSuccess={() => {
onSuccess(item)
dismiss()
}}
/>
),
})
}}
>
Follow
</Button>
)}
<div className="ml-6 text-zinc-500">
<span className="font-medium text-zinc-800 dark:text-zinc-200">
{item.subscriptionCount}
</span>{" "}
Followers
</div>
</CardFooter>
</>
)}
</Card>
)
})

0 comments on commit 32a55ec

Please sign in to comment.