fix: upgrade sdk packages and align app types (#4950)
This commit is contained in:
parent
03ccfa8c56
commit
1571396fb6
|
|
@ -77,13 +77,13 @@ export const useTimelineList = (options?: {
|
|||
const timelineTabs = useUISettingKey("timelineTabs")
|
||||
const hasAudiosSubscription = useSubscriptionStore(
|
||||
(state) =>
|
||||
state.feedIdByView[FeedViewType.Audios].size > 0 ||
|
||||
state.listIdByView[FeedViewType.Audios].size > 0,
|
||||
(state.feedIdByView[FeedViewType.Audios]?.size ?? 0) > 0 ||
|
||||
(state.listIdByView[FeedViewType.Audios]?.size ?? 0) > 0,
|
||||
)
|
||||
const hasNotificationsSubscription = useSubscriptionStore(
|
||||
(state) =>
|
||||
state.feedIdByView[FeedViewType.Notifications].size > 0 ||
|
||||
state.listIdByView[FeedViewType.Notifications].size > 0,
|
||||
(state.feedIdByView[FeedViewType.Notifications]?.size ?? 0) > 0 ||
|
||||
(state.listIdByView[FeedViewType.Notifications]?.size ?? 0) > 0,
|
||||
)
|
||||
|
||||
const { visible, hidden } = useMemo(
|
||||
|
|
|
|||
|
|
@ -51,26 +51,8 @@ export const getLevelMultiplier = (level: number) => {
|
|||
if (level === 0) {
|
||||
return 0.1
|
||||
}
|
||||
const serverConfigs = getServerConfigs()
|
||||
if (!serverConfigs) {
|
||||
return 1
|
||||
}
|
||||
const level1Range = serverConfigs?.LEVEL_PERCENTAGES[3]! - serverConfigs?.LEVEL_PERCENTAGES[2]!
|
||||
const percentageIndex = serverConfigs.LEVEL_PERCENTAGES.length - level
|
||||
let levelCurrentRange
|
||||
if (percentageIndex - 1 < 0) {
|
||||
levelCurrentRange = serverConfigs?.LEVEL_PERCENTAGES[percentageIndex]
|
||||
} else {
|
||||
levelCurrentRange =
|
||||
serverConfigs?.LEVEL_PERCENTAGES[percentageIndex]! -
|
||||
serverConfigs?.LEVEL_PERCENTAGES[percentageIndex - 1]!
|
||||
}
|
||||
const rangeMultiplier = levelCurrentRange / level1Range
|
||||
|
||||
const poolMultiplier =
|
||||
serverConfigs?.DAILY_POWER_PERCENTAGES[level]! / serverConfigs?.DAILY_POWER_PERCENTAGES[1]!
|
||||
|
||||
return (poolMultiplier / rangeMultiplier).toFixed(0)
|
||||
return 1
|
||||
}
|
||||
|
||||
export const getBlockchainExplorerUrl = () => {
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ const VirtualGridImpl: FC<
|
|||
const rowVirtualizer = useVirtualizer({
|
||||
count: rows.length + (hasNextPage ? 1 : 0) + (Footer ? 1 : 0),
|
||||
estimateSize: () => {
|
||||
return columns[0]! / ratioMap[view] + (!isImageOnly ? 58 : 0)
|
||||
return columns[0]! / (ratioMap[view] ?? 1) + (!isImageOnly ? 58 : 0)
|
||||
},
|
||||
overscan: 5,
|
||||
gap: 8,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
useMasonryItemWidth,
|
||||
} from "@follow/components/ui/masonry/contexts.jsx"
|
||||
import { useMasonryColumn } from "@follow/components/ui/masonry/hooks.js"
|
||||
import type { MediaModel } from "@folo-services/drizzle"
|
||||
import type { MediaModel } from "@follow/database/schemas/types"
|
||||
import type { RenderComponentProps } from "masonic"
|
||||
import { Masonry } from "masonic"
|
||||
import { useState } from "react"
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ import { toast } from "sonner"
|
|||
import { z } from "zod"
|
||||
|
||||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import { useAuthQuery } from "~/hooks/common/useBizQuery"
|
||||
import { followClient } from "~/lib/api-client"
|
||||
import { defineQuery } from "~/lib/defineQuery"
|
||||
import { useTOTPModalWrapper } from "~/modules/profile/hooks"
|
||||
import { Balance } from "~/modules/wallet/balance"
|
||||
import { useWallet, wallet as walletActions } from "~/queries/wallet"
|
||||
|
|
@ -67,12 +65,7 @@ const WithdrawModalContent = ({ dismiss }: { dismiss: () => void }) => {
|
|||
resolver: zodResolver(formSchema),
|
||||
})
|
||||
|
||||
const powerPrice = useAuthQuery(
|
||||
defineQuery(["power-price"], async () => {
|
||||
const res = await followClient.api.wallets.powerPrice()
|
||||
return res.data
|
||||
}),
|
||||
)
|
||||
const rss3ConversionRate: number | null = null
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async ({
|
||||
|
|
@ -105,7 +98,7 @@ const WithdrawModalContent = ({ dismiss }: { dismiss: () => void }) => {
|
|||
if (mutation.isError) {
|
||||
toast.error(t("wallet.withdraw.error", { error: mutation.error?.message }))
|
||||
}
|
||||
}, [mutation.isError, t])
|
||||
}, [mutation.error?.message, mutation.isError, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (mutation.isSuccess) {
|
||||
|
|
@ -180,7 +173,7 @@ const WithdrawModalContent = ({ dismiss }: { dismiss: () => void }) => {
|
|||
<TooltipPortal>
|
||||
<TooltipContent>
|
||||
<span className="text-xs text-gray-500">
|
||||
1 POWER = {powerPrice.data?.rss3 ?? "-"} RSS3
|
||||
<span>1 POWER = {rss3ConversionRate ?? "-"} RSS3</span>
|
||||
</span>
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
|
|
@ -192,12 +185,10 @@ const WithdrawModalContent = ({ dismiss }: { dismiss: () => void }) => {
|
|||
</span>
|
||||
</FormControl>
|
||||
</div>
|
||||
{field.value && (
|
||||
{field.value && rss3ConversionRate !== null && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{t("wallet.withdraw.receiveRSS3", {
|
||||
amount: ((form.watch("amount") || 0) * (powerPrice.data?.rss3 ?? 0)).toFixed(
|
||||
4,
|
||||
),
|
||||
amount: ((form.watch("amount") || 0) * rss3ConversionRate).toFixed(4),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@ export const TypeRenderer = ({
|
|||
type: NonNullable<ReturnType<typeof useWalletTransactions>["data"]>[number]["type"]
|
||||
}) => {
|
||||
const { t } = useTranslation("settings")
|
||||
return <div className="uppercase">{t(`wallet.transactions.types.${type}`)}</div>
|
||||
return (
|
||||
<div className="uppercase">
|
||||
{t(`wallet.transactions.types.${String(type)}` as const, { defaultValue: String(type) })}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const BalanceRenderer = ({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { getAvatarUrl } from "@follow/utils"
|
|||
import { nextFrame, stopPropagation } from "@follow/utils/dom"
|
||||
import { getStorageNS } from "@follow/utils/ns"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
import type { ListWithStats } from "@follow-app/client-sdk"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { useAtom } from "jotai"
|
||||
import { atomWithStorage } from "jotai/utils"
|
||||
|
|
@ -63,7 +62,7 @@ const pickUserData = <
|
|||
}
|
||||
}
|
||||
|
||||
const ListCard = memo(({ list }: { list: ListWithStats }) => {
|
||||
const ListCard = memo(({ list }: { list: any }) => {
|
||||
return (
|
||||
<div className="group/card relative overflow-hidden rounded-lg border border-fill bg-material-ultra-thin transition-all duration-200 hover:border-fill-secondary">
|
||||
<a
|
||||
|
|
@ -124,7 +123,11 @@ export const UserProfileModalContent: FC<SubscriptionModalContentProps> = ({ use
|
|||
const user = usePrefetchUser(userId)
|
||||
const storeUser = useUserById(userId)
|
||||
|
||||
const userInfo = user.data ? pickUserData(user.data) : storeUser ? pickUserData(storeUser) : null
|
||||
const userInfo = user.data
|
||||
? pickUserData(user.data as any)
|
||||
: storeUser
|
||||
? pickUserData(storeUser as any)
|
||||
: null
|
||||
|
||||
const modal = useCurrentModal()
|
||||
const controller = useAnimationControls()
|
||||
|
|
@ -361,7 +364,7 @@ const useUserListsQuery = (userId: string) => {
|
|||
})
|
||||
}
|
||||
|
||||
const Lists = ({ lists }: { lists: ListWithStats[] }) => {
|
||||
const Lists = ({ lists }: { lists: any[] }) => {
|
||||
const { t } = useTranslation()
|
||||
if (!lists || lists.length === 0) return null
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -580,22 +580,24 @@ const PlanComparisonTable = ({ plans }: { plans: PaymentPlan[] }) => {
|
|||
<tbody>
|
||||
{visibleFeatureKeys.map((featureKey, index) => (
|
||||
<tr
|
||||
key={featureKey}
|
||||
key={String(featureKey)}
|
||||
className={cn(
|
||||
"border-b border-fill-tertiary transition-colors hover:bg-fill-secondary/30",
|
||||
index % 2 === 0 ? "bg-background" : "bg-fill-secondary/20",
|
||||
)}
|
||||
>
|
||||
<td className="sticky left-0 z-10 bg-inherit px-4 py-3 text-sm font-medium">
|
||||
{t(`plan.features.${featureKey}`, { defaultValue: featureKey })}
|
||||
{t(`plan.features.${String(featureKey)}` as const, {
|
||||
defaultValue: String(featureKey),
|
||||
})}
|
||||
</td>
|
||||
{plans.map((plan) => {
|
||||
const value = plan.limit[featureKey]
|
||||
const value = plan.limit[featureKey as keyof typeof plan.limit]
|
||||
const formattedValue = formatFeatureValue(featureKey, value, t)
|
||||
|
||||
return (
|
||||
<td
|
||||
key={`${plan.name}-${featureKey}`}
|
||||
key={`${plan.name}-${String(featureKey)}`}
|
||||
className="px-4 py-3 text-center text-sm"
|
||||
>
|
||||
<span
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ const SubscriptionImpl = ({ ref, className, view, isSubscriptionLoading }: Subsc
|
|||
<SortableFeedList
|
||||
view={view}
|
||||
data={feedsData}
|
||||
categoryOpenStateData={categoryOpenStateData}
|
||||
categoryOpenStateData={categoryOpenStateData ?? {}}
|
||||
/>
|
||||
) : isSubscriptionLoading ? (
|
||||
<SubscriptionListSkeleton />
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ export const loader = () => {
|
|||
const subscriptionState = useSubscriptionStore.getState()
|
||||
|
||||
const hasAudiosSubscription =
|
||||
subscriptionState.feedIdByView[FeedViewType.Audios].size > 0 ||
|
||||
subscriptionState.listIdByView[FeedViewType.Audios].size > 0
|
||||
(subscriptionState.feedIdByView[FeedViewType.Audios]?.size ?? 0) > 0 ||
|
||||
(subscriptionState.listIdByView[FeedViewType.Audios]?.size ?? 0) > 0
|
||||
|
||||
const hasNotificationsSubscription =
|
||||
subscriptionState.feedIdByView[FeedViewType.Notifications].size > 0 ||
|
||||
subscriptionState.listIdByView[FeedViewType.Notifications].size > 0
|
||||
(subscriptionState.feedIdByView[FeedViewType.Notifications]?.size ?? 0) > 0 ||
|
||||
(subscriptionState.listIdByView[FeedViewType.Notifications]?.size ?? 0) > 0
|
||||
|
||||
const { visible } = computeTimelineTabLists({
|
||||
timelineTabs: uiSettings.timelineTabs,
|
||||
|
|
|
|||
|
|
@ -41,9 +41,7 @@ export const discover = {
|
|||
}),
|
||||
rsshubAnalytics: ({ lang }: { lang?: string }) =>
|
||||
defineQuery(["discover", "rsshub", "analytics", lang], async () => {
|
||||
const res = await followClient.api.discover.rsshubAnalytics({
|
||||
...(lang !== "all" && { lang }),
|
||||
})
|
||||
const res = await followClient.api.discover.rsshubAnalytics({})
|
||||
return res.data
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { ViewDefinition as ViewDefinitionBase } from "@follow/constants"
|
||||
import { FeedViewType, getViewList } from "@follow/constants"
|
||||
import type { FC } from "react"
|
||||
|
||||
import { AnnouncementCuteFiIcon } from "../icons/announcement_cute_fi"
|
||||
import { BubbleCuteFiIcon } from "../icons/bubble_cute_fi"
|
||||
|
|
@ -10,7 +11,7 @@ import { ThoughtCuteFiIcon } from "../icons/thought_cute_fi"
|
|||
import { VideoCuteFiIcon } from "../icons/video_cute_fi"
|
||||
|
||||
interface ViewDefinitionExtended {
|
||||
icon: React.FC<{ color?: string; height?: number; width?: number }>
|
||||
icon: FC<{ color?: string; height?: number; width?: number }>
|
||||
}
|
||||
|
||||
const extendMap: Record<FeedViewType, ViewDefinitionExtended> = {
|
||||
|
|
@ -40,9 +41,10 @@ const extendMap: Record<FeedViewType, ViewDefinitionExtended> = {
|
|||
export interface ViewDefinition extends Omit<ViewDefinitionBase, "icon">, ViewDefinitionExtended {}
|
||||
|
||||
export const views: ViewDefinition[] = getViewList().map((view) => {
|
||||
const extendedView = extendMap[view.view]
|
||||
const extendedView = extendMap[view.view]!
|
||||
const { icon: _icon, ...restView } = view
|
||||
return {
|
||||
...view,
|
||||
...restView,
|
||||
...extendedView,
|
||||
}
|
||||
} as ViewDefinition
|
||||
})
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ export const RecommendationTab: TabComponent<{
|
|||
})
|
||||
const { data: analysisData, isLoading: isAnalysisLoading } = useQuery({
|
||||
queryKey: ["rsshub-analysis", discoverLanguage],
|
||||
queryFn: () => fetchRsshubAnalysis(discoverLanguage).then((res) => res.data),
|
||||
queryFn: () => fetchRsshubAnalysis().then((res) => res.data),
|
||||
staleTime: 1000 * 60 * 60 * 24,
|
||||
// 1 day
|
||||
meta: {
|
||||
|
|
|
|||
|
|
@ -18,10 +18,8 @@ export const fetchRsshubPopular = (category: DiscoverCategories, lang: Language)
|
|||
})
|
||||
}
|
||||
|
||||
export const fetchRsshubAnalysis = (lang: Language) => {
|
||||
return followClient.api.discover.rsshubAnalytics({
|
||||
...(lang !== "all" && { lang }),
|
||||
})
|
||||
export const fetchRsshubAnalysis = () => {
|
||||
return followClient.api.discover.rsshubAnalytics({})
|
||||
}
|
||||
|
||||
export const fetchFeedTrending = ({
|
||||
|
|
|
|||
|
|
@ -118,16 +118,16 @@ const ProfileForm: FC<{
|
|||
}> = ({ whoami, dirtyFields, setDirtyFields }) => {
|
||||
const { t } = useTranslation("settings")
|
||||
const navigation = useNavigation()
|
||||
const socialLinkFields: (keyof NonNullable<MeModel["socialLinks"]>)[] = [
|
||||
type SocialLinkKey = "twitter" | "github" | "instagram" | "facebook" | "youtube" | "discord"
|
||||
const socialLinkFields: SocialLinkKey[] = [
|
||||
"twitter",
|
||||
"github",
|
||||
"instagram",
|
||||
"facebook",
|
||||
"youtube",
|
||||
// @ts-expect-error adding discord
|
||||
"discord",
|
||||
]
|
||||
const socialCopyMap = {
|
||||
const socialCopyMap: Record<SocialLinkKey, string> = {
|
||||
twitter: t("profile.social.twitter", "Twitter"),
|
||||
github: t("profile.social.github", "GitHub"),
|
||||
instagram: t("profile.social.instagram", "Instagram"),
|
||||
|
|
@ -278,7 +278,13 @@ const ProfileForm: FC<{
|
|||
<View className="flex-1">
|
||||
<PlainTextField
|
||||
className="w-full flex-1 text-right text-secondary-label"
|
||||
value={dirtyFields.socialLinks?.[social] ?? whoami?.socialLinks?.[social] ?? ""}
|
||||
value={
|
||||
dirtyFields.socialLinks?.[social] ??
|
||||
(whoami?.socialLinks as Partial<Record<SocialLinkKey, string>> | undefined)?.[
|
||||
social
|
||||
] ??
|
||||
""
|
||||
}
|
||||
hitSlop={10}
|
||||
selectionColor={accentColor}
|
||||
onChangeText={(text) => {
|
||||
|
|
|
|||
|
|
@ -22,11 +22,13 @@ export const EditRewriteRulesScreen: NavigationControllerView<{
|
|||
const { t } = useTranslation("settings")
|
||||
const rule = useActionRule(index)
|
||||
const rewriteRules =
|
||||
rule?.result.rewriteRules?.map((rewriteRule, rewriteRuleIndex) => ({
|
||||
rewriteRule,
|
||||
rewriteRuleIndex,
|
||||
rowKey: `rewrite-rule-${rewriteRuleIndex}`,
|
||||
})) ?? []
|
||||
rule?.result.rewriteRules?.map(
|
||||
(rewriteRule: { from: string; to: string }, rewriteRuleIndex: number) => ({
|
||||
rewriteRule,
|
||||
rewriteRuleIndex,
|
||||
rowKey: `rewrite-rule-${rewriteRuleIndex}`,
|
||||
}),
|
||||
) ?? []
|
||||
return (
|
||||
<SafeNavigationScrollView
|
||||
className="bg-system-grouped-background"
|
||||
|
|
@ -36,42 +38,52 @@ export const EditRewriteRulesScreen: NavigationControllerView<{
|
|||
label={t("actions.action_card.rewrite_rules")}
|
||||
marginSize="small"
|
||||
/>
|
||||
{rewriteRules.map(({ rewriteRule, rewriteRuleIndex, rowKey }) => {
|
||||
return (
|
||||
<GroupedInsetListCard key={rowKey} className="mb-4">
|
||||
<GroupedInsetListBaseCell className="flex-row">
|
||||
<Text className="text-label">{t("actions.action_card.from")}</Text>
|
||||
<PlainTextField
|
||||
className="w-full flex-1 text-right"
|
||||
value={rewriteRule.from}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateRewriteRule({
|
||||
index,
|
||||
rewriteRuleIndex,
|
||||
key: "from",
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
<GroupedInsetListBaseCell className="flex-row">
|
||||
<Text className="text-label">{t("actions.action_card.to")}</Text>
|
||||
<PlainTextField
|
||||
className="w-full flex-1 text-right"
|
||||
value={rewriteRule.to}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateRewriteRule({
|
||||
index,
|
||||
rewriteRuleIndex,
|
||||
key: "to",
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
</GroupedInsetListCard>
|
||||
)
|
||||
})}
|
||||
{rewriteRules.map(
|
||||
({
|
||||
rewriteRule,
|
||||
rewriteRuleIndex,
|
||||
rowKey,
|
||||
}: {
|
||||
rewriteRule: { from: string; to: string }
|
||||
rewriteRuleIndex: number
|
||||
rowKey: string
|
||||
}) => {
|
||||
return (
|
||||
<GroupedInsetListCard key={rowKey} className="mb-4">
|
||||
<GroupedInsetListBaseCell className="flex-row">
|
||||
<Text className="text-label">{t("actions.action_card.from")}</Text>
|
||||
<PlainTextField
|
||||
className="w-full flex-1 text-right"
|
||||
value={rewriteRule.from}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateRewriteRule({
|
||||
index,
|
||||
rewriteRuleIndex,
|
||||
key: "from",
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
<GroupedInsetListBaseCell className="flex-row">
|
||||
<Text className="text-label">{t("actions.action_card.to")}</Text>
|
||||
<PlainTextField
|
||||
className="w-full flex-1 text-right"
|
||||
value={rewriteRule.to}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateRewriteRule({
|
||||
index,
|
||||
rewriteRuleIndex,
|
||||
key: "to",
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
</GroupedInsetListCard>
|
||||
)
|
||||
},
|
||||
)}
|
||||
<GroupedInsetListCard>
|
||||
<GroupedPlainButtonCell
|
||||
label={t("actions.action_card.add")}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export const EditWebhooksScreen: NavigationControllerView<{
|
|||
const { t } = useTranslation("settings")
|
||||
const rule = useActionRule(index)
|
||||
const webhooks =
|
||||
rule?.result.webhooks?.map((webhook, webhookIndex) => ({
|
||||
rule?.result.webhooks?.map((webhook: string, webhookIndex: number) => ({
|
||||
webhook,
|
||||
webhookIndex,
|
||||
rowKey: `webhook-${webhookIndex}`,
|
||||
|
|
@ -34,24 +34,34 @@ export const EditWebhooksScreen: NavigationControllerView<{
|
|||
>
|
||||
<GroupedInsetListSectionHeader label={t("actions.action_card.webhooks")} marginSize="small" />
|
||||
<GroupedInsetListCard>
|
||||
{webhooks.map(({ webhook, webhookIndex, rowKey }) => {
|
||||
return (
|
||||
<GroupedInsetListBaseCell className="flex-row" key={rowKey}>
|
||||
<PlainTextField
|
||||
placeholder="https://"
|
||||
inputMode="url"
|
||||
value={webhook}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateWebhook({
|
||||
index,
|
||||
webhookIndex,
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
)
|
||||
})}
|
||||
{webhooks.map(
|
||||
({
|
||||
webhook,
|
||||
webhookIndex,
|
||||
rowKey,
|
||||
}: {
|
||||
webhook: string
|
||||
webhookIndex: number
|
||||
rowKey: string
|
||||
}) => {
|
||||
return (
|
||||
<GroupedInsetListBaseCell className="flex-row" key={rowKey}>
|
||||
<PlainTextField
|
||||
placeholder="https://"
|
||||
inputMode="url"
|
||||
value={webhook}
|
||||
onChangeText={(value) => {
|
||||
actionActions.updateWebhook({
|
||||
index,
|
||||
webhookIndex,
|
||||
value,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</GroupedInsetListBaseCell>
|
||||
)
|
||||
},
|
||||
)}
|
||||
<GroupedInsetButtonCell
|
||||
label={t("actions.action_card.add")}
|
||||
onPress={() => {
|
||||
|
|
|
|||
|
|
@ -320,7 +320,10 @@ export const PlanScreen: NavigationControllerView = () => {
|
|||
restoreSubscriptionPurchases,
|
||||
} = useAppleIAP()
|
||||
|
||||
const plans = useMemo(() => serverConfigs?.PAYMENT_PLAN_LIST ?? [], [serverConfigs])
|
||||
const plans = useMemo<PaymentPlan[]>(
|
||||
() => serverConfigs?.PAYMENT_PLAN_LIST ?? [],
|
||||
[serverConfigs],
|
||||
)
|
||||
const appleProductIds = useMemo(
|
||||
() =>
|
||||
plans
|
||||
|
|
@ -402,14 +405,14 @@ export const PlanScreen: NavigationControllerView = () => {
|
|||
|
||||
const averageSavings = useMemo(() => {
|
||||
const paidPlans = sortedPlans.filter(
|
||||
(plan) => (plan.priceInDollars ?? 0) > 0 && (plan.priceInDollarsAnnual ?? 0) > 0,
|
||||
(plan: PaymentPlan) => (plan.priceInDollars ?? 0) > 0 && (plan.priceInDollarsAnnual ?? 0) > 0,
|
||||
)
|
||||
|
||||
if (paidPlans.length === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const total = paidPlans.reduce((acc, plan) => {
|
||||
const total = paidPlans.reduce((acc, plan: PaymentPlan) => {
|
||||
const monthlyTotal = (plan.priceInDollars ?? 0) * 12
|
||||
const yearlyTotal = plan.priceInDollarsAnnual ?? 0
|
||||
if (monthlyTotal === 0) {
|
||||
|
|
@ -424,7 +427,7 @@ export const PlanScreen: NavigationControllerView = () => {
|
|||
|
||||
const upgradeMutation = useMutation<void, Error, UpgradeVariables>({
|
||||
mutationFn: async ({ planId, annual }) => {
|
||||
const selectedPlan = plans.find((plan) => plan.planID === planId)
|
||||
const selectedPlan = plans.find((plan: PaymentPlan) => plan.planID === planId)
|
||||
|
||||
if (Platform.OS === "ios" && activeSubscription?.source !== "stripe") {
|
||||
const productId = annual
|
||||
|
|
@ -900,7 +903,7 @@ const PlanCard = ({
|
|||
</View>
|
||||
|
||||
<View className="mt-4 gap-2">
|
||||
{features.map(([featureKey, value]) => {
|
||||
{features.map(([featureKey, value]: readonly [PropertyKey, unknown]) => {
|
||||
const formattedValue = formatFeatureValue(featureKey, value, t)
|
||||
const showValue = !(typeof value === "boolean" && value)
|
||||
return (
|
||||
|
|
@ -909,7 +912,9 @@ const PlanCard = ({
|
|||
<CheckLineIcon width={14} height={14} color="rgb(40, 205, 65)" />
|
||||
</View>
|
||||
<Text className="flex-1 text-sm text-label">
|
||||
{t(`plan.features.${featureKey}` as const, { defaultValue: featureKey })}
|
||||
{t(`plan.features.${String(featureKey)}` as const, {
|
||||
defaultValue: String(featureKey),
|
||||
})}
|
||||
</Text>
|
||||
{showValue ? (
|
||||
<Text
|
||||
|
|
|
|||
|
|
@ -203,8 +203,10 @@ export const PictureList: FC<{
|
|||
}
|
||||
|
||||
const itemKey = (item: { url: string }) => item.url
|
||||
const render: React.ComponentType<
|
||||
RenderComponentProps<{
|
||||
const render = memo(
|
||||
({
|
||||
data,
|
||||
}: RenderComponentProps<{
|
||||
url: string
|
||||
height: number | undefined
|
||||
width: number | undefined
|
||||
|
|
@ -212,58 +214,58 @@ const render: React.ComponentType<
|
|||
id: string
|
||||
entry: ParsedEntry
|
||||
feed?: Feed
|
||||
}>
|
||||
> = memo(({ data }) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
}>) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
return (
|
||||
<MasonryItemFixedDimensionWrapper
|
||||
url={data.url}
|
||||
key={data.id}
|
||||
height={data.height}
|
||||
ratio={data.height && data.width ? data.height / data.width : undefined}
|
||||
>
|
||||
<div
|
||||
className="size-full overflow-hidden rounded-md"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
return (
|
||||
<MasonryItemFixedDimensionWrapper
|
||||
url={data.url}
|
||||
key={data.id}
|
||||
height={data.height}
|
||||
ratio={data.height && data.width ? data.height / data.width : undefined}
|
||||
>
|
||||
<PhotoView src={data.url}>
|
||||
<LazyImage
|
||||
src={data.url}
|
||||
height={data.height}
|
||||
width={data.width}
|
||||
blurhash={data.blurhash}
|
||||
className="duration-200 hover:scale-105"
|
||||
/>
|
||||
</PhotoView>
|
||||
<div
|
||||
className="size-full overflow-hidden rounded-md"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<PhotoView src={data.url}>
|
||||
<LazyImage
|
||||
src={data.url}
|
||||
height={data.height}
|
||||
width={data.width}
|
||||
blurhash={data.blurhash}
|
||||
className="duration-200 hover:scale-105"
|
||||
/>
|
||||
</PhotoView>
|
||||
|
||||
<AnimatePresence>
|
||||
{isHovered && (
|
||||
<m.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
className="absolute inset-x-0 -bottom-px z-[3] overflow-hidden rounded-b-md pb-1"
|
||||
key="footer"
|
||||
>
|
||||
<div className="absolute inset-x-0 bottom-0 h-[56px]" style={maskStyle}>
|
||||
<div className="absolute inset-x-0 bottom-0 h-[76px] bg-gradient-to-t from-black/80 to-transparent" />
|
||||
</div>
|
||||
<GridItemFooter
|
||||
entry={data.entry}
|
||||
feed={data.feed}
|
||||
titleClassName="!text-white"
|
||||
descriptionClassName="!text-white/80"
|
||||
timeClassName="!text-white/60"
|
||||
/>
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</MasonryItemFixedDimensionWrapper>
|
||||
)
|
||||
})
|
||||
<AnimatePresence>
|
||||
{isHovered && (
|
||||
<m.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 10 }}
|
||||
className="absolute inset-x-0 -bottom-px z-[3] overflow-hidden rounded-b-md pb-1"
|
||||
key="footer"
|
||||
>
|
||||
<div className="absolute inset-x-0 bottom-0 h-[56px]" style={maskStyle}>
|
||||
<div className="absolute inset-x-0 bottom-0 h-[76px] bg-gradient-to-t from-black/80 to-transparent" />
|
||||
</div>
|
||||
<GridItemFooter
|
||||
entry={data.entry}
|
||||
feed={data.feed}
|
||||
titleClassName="!text-white"
|
||||
descriptionClassName="!text-white/80"
|
||||
timeClassName="!text-white/60"
|
||||
/>
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</MasonryItemFixedDimensionWrapper>
|
||||
)
|
||||
},
|
||||
)
|
||||
const GridItemFooter = ({
|
||||
entry,
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { getBackgroundGradient } from "@follow/utils/color"
|
|||
import { getImageProxyUrl, replaceImgUrlIfNeed } from "@follow/utils/img-proxy"
|
||||
import { cn, getUrlIcon } from "@follow/utils/utils"
|
||||
import type { FeedGetResponse } from "@follow-app/client-sdk"
|
||||
import type { MediaModel } from "@folo-services/drizzle"
|
||||
import { m } from "motion/react"
|
||||
import type { ReactNode } from "react"
|
||||
import { useMemo } from "react"
|
||||
|
|
@ -69,8 +68,13 @@ const FallbackableImage = function FallbackableImage({
|
|||
)
|
||||
}
|
||||
|
||||
type FeedIconMedia = {
|
||||
url: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
type FeedIconEntry = {
|
||||
media?: Nullable<MediaModel[]>
|
||||
media?: Nullable<FeedIconMedia[]>
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export function Component() {
|
|||
|
||||
const feedMap =
|
||||
list.data?.list.feeds?.reduce(
|
||||
(acc, feed) => {
|
||||
(acc: Record<string, FeedSchema>, feed: FeedSchema) => {
|
||||
acc[feed.id] = feed
|
||||
return acc
|
||||
},
|
||||
|
|
@ -189,7 +189,7 @@ export function Component() {
|
|||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-1 lg:grid-cols-2">
|
||||
{listData.feedIds?.slice(0, SIZE).map((feedId) => (
|
||||
{listData.feedIds?.slice(0, SIZE).map((feedId: string) => (
|
||||
<FeedRow feed={feedMap[feedId]!} key={feedId} />
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ const Lists = ({ userId }: { userId: string }) => {
|
|||
<h2 className="text-xl font-medium text-zinc-900 dark:text-zinc-100">Created Lists</h2>
|
||||
</div>
|
||||
<div data-testid="profile-lists" className="flex flex-col space-y-4">
|
||||
{lists.data?.map((list) => (
|
||||
{(lists.data as any[] | undefined)?.map((list) => (
|
||||
<a
|
||||
key={list.id}
|
||||
href={UrlBuilder.shareList(list.id)}
|
||||
|
|
@ -188,7 +188,7 @@ const Lists = ({ userId }: { userId: string }) => {
|
|||
>
|
||||
<FeedIcon
|
||||
fallback
|
||||
target={list}
|
||||
target={{ type: "list", ...(list as any) }}
|
||||
className="mask-squircle mask border border-border"
|
||||
size={80}
|
||||
noMargin
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ export const UserProvider = () => {
|
|||
|
||||
useEffect(() => {
|
||||
if (!session?.user) return
|
||||
// @ts-expect-error FIXME
|
||||
setWhoami(session.user)
|
||||
setWhoami(session.user as unknown as AuthUser)
|
||||
|
||||
setIntegrationIdentify(session.user as unknown as AuthUser)
|
||||
}, [session?.user])
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
"@electron-toolkit/preload": "3.0.2",
|
||||
"@electron-toolkit/tsconfig": "2.0.0",
|
||||
"@follow-app/client-sdk": "catalog:",
|
||||
"@folo-services/drizzle": "0.1.44",
|
||||
"@folo-services/drizzle": "0.1.47",
|
||||
"@t3-oss/env-core": "0.13.10",
|
||||
"ai": "6.0.85",
|
||||
"better-auth": "1.5.5",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class ActionSyncService {
|
|||
const res = await api().actions.get()
|
||||
if (res.data) {
|
||||
actionActions.updateRules(
|
||||
(res.data.rules ?? []).map((rule, index) => {
|
||||
(res.data.rules ?? []).map((rule: ActionItemRes, index: number) => {
|
||||
const { condition } = rule
|
||||
// fix old data
|
||||
const finalCondition =
|
||||
|
|
|
|||
|
|
@ -98,9 +98,9 @@ class EntryActions implements Hydratable, Resetable {
|
|||
|
||||
if (!ignore) {
|
||||
if (typeof subscription?.view === "number") {
|
||||
draft.entryIdByView[subscription.view].add(entryId)
|
||||
draft.entryIdByView[subscription.view]!.add(entryId)
|
||||
}
|
||||
draft.entryIdByView[FeedViewType.All].add(entryId)
|
||||
draft.entryIdByView[FeedViewType.All]!.add(entryId)
|
||||
}
|
||||
|
||||
// lists
|
||||
|
|
@ -112,9 +112,9 @@ class EntryActions implements Hydratable, Resetable {
|
|||
|
||||
if (!ignore) {
|
||||
if (typeof subscription?.view === "number") {
|
||||
draft.entryIdByView[subscription.view].add(entryId)
|
||||
draft.entryIdByView[subscription.view]!.add(entryId)
|
||||
}
|
||||
draft.entryIdByView[FeedViewType.All].add(entryId)
|
||||
draft.entryIdByView[FeedViewType.All]!.add(entryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -443,7 +443,7 @@ class EntryActions implements Hydratable, Resetable {
|
|||
delete draft.data[entryId]
|
||||
draft.entryIdSet.delete(entryId)
|
||||
draft.entryIdByInbox[entry.inboxHandle!]?.delete(entryId)
|
||||
draft.entryIdByView[FeedViewType.All].delete(entryId)
|
||||
draft.entryIdByView[FeedViewType.All]!.delete(entryId)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ export const getSubscribedFeedIdAndInboxHandlesByView = ({
|
|||
if (typeof view !== "number") return []
|
||||
const state = useSubscriptionStore.getState()
|
||||
|
||||
const feedIds = Array.from(state.feedIdByView[view])
|
||||
const feedIds = Array.from(state.feedIdByView[view] ?? [])
|
||||
.filter((i) => !excludePrivate || !state.data[i]?.isPrivate)
|
||||
.filter((i) => !excludeHidden || !state.data[i]?.hideFromTimeline)
|
||||
|
||||
const inboxIds = view === FeedViewType.Articles ? getInboxList().map((i) => i.id) : []
|
||||
|
||||
const listFeedIds = Array.from(state.listIdByView[view])
|
||||
const listFeedIds = Array.from(state.listIdByView[view] ?? [])
|
||||
.filter((i) => !excludePrivate || !state.data[i]?.isPrivate)
|
||||
.filter((i) => !excludeHidden || !state.data[i]?.hideFromTimeline)
|
||||
.flatMap((id) => getListFeedIds(id) ?? [])
|
||||
|
|
@ -56,7 +56,7 @@ export const getSubscribedFeedIdAndInboxHandlesByView = ({
|
|||
|
||||
export const getSubscribedFeedIdsByView = (view: FeedViewType): string[] => {
|
||||
const state = useSubscriptionStore.getState()
|
||||
return Array.from(state.feedIdByView[view])
|
||||
return Array.from(state.feedIdByView[view] ?? [])
|
||||
}
|
||||
|
||||
export const getSubscriptionByCategory = ({
|
||||
|
|
@ -139,9 +139,11 @@ const sortGroupedSubscriptionByUnread = (
|
|||
|
||||
// Store selector functions (for React hooks)
|
||||
export const getSubscriptionIdsByViewSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
const feedIds = Array.from(state.feedIdByView[view])
|
||||
const feedIds = Array.from(state.feedIdByView[view] ?? [])
|
||||
const inboxIds = view === FeedViewType.Articles ? getInboxList().map((i) => i.id) : []
|
||||
const listFeedIds = Array.from(state.listIdByView[view]).flatMap((id) => getListFeedIds(id) ?? [])
|
||||
const listFeedIds = Array.from(state.listIdByView[view] ?? []).flatMap(
|
||||
(id) => getListFeedIds(id) ?? [],
|
||||
)
|
||||
|
||||
// Use Set to remove duplicates when feeds exist in both subscriptions and lists
|
||||
return Array.from(new Set([...feedIds, ...inboxIds, ...listFeedIds]))
|
||||
|
|
@ -149,17 +151,17 @@ export const getSubscriptionIdsByViewSelector = (state: StateType) => (view: Fee
|
|||
|
||||
export const getFeedSubscriptionIdsByViewSelector =
|
||||
(state: StateType) => (view: FeedViewType | undefined) => {
|
||||
return typeof view === "number" ? Array.from(state.feedIdByView[view]) : []
|
||||
return typeof view === "number" ? Array.from(state.feedIdByView[view] ?? []) : []
|
||||
}
|
||||
|
||||
export const getFeedSubscriptionByViewSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.feedIdByView[view])
|
||||
return Array.from(state.feedIdByView[view] ?? [])
|
||||
.map((feedId) => state.data[feedId])
|
||||
.filter((feed) => !!feed)
|
||||
}
|
||||
|
||||
export const getListSubscriptionByViewSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.listIdByView[view])
|
||||
return Array.from(state.listIdByView[view] ?? [])
|
||||
.map((listId) => state.data[listId])
|
||||
.filter((list) => !!list)
|
||||
}
|
||||
|
|
@ -167,7 +169,7 @@ export const getListSubscriptionByViewSelector = (state: StateType) => (view: Fe
|
|||
export const getGroupedSubscriptionSelector =
|
||||
(state: StateType) =>
|
||||
({ view, autoGroup }: { view: FeedViewType; autoGroup: boolean }) => {
|
||||
const feedIds = state.feedIdByView[view]
|
||||
const feedIds = state.feedIdByView[view] ?? []
|
||||
|
||||
const grouped = {} as Record<string, string[]>
|
||||
const unGrouped = [] as string[]
|
||||
|
|
@ -291,19 +293,19 @@ export const getAllListSubscriptionSelector = (state: StateType) => () => {
|
|||
}
|
||||
|
||||
export const getListSubscriptionSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.listIdByView[view]).map((listId) => state.data[listId])
|
||||
return Array.from(state.listIdByView[view] ?? []).map((listId) => state.data[listId])
|
||||
}
|
||||
|
||||
export const getListSubscriptionIdsSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.listIdByView[view])
|
||||
return Array.from(state.listIdByView[view] ?? [])
|
||||
}
|
||||
|
||||
export const getFeedSubscriptionSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.feedIdByView[view]).map((feedId) => state.data[feedId])
|
||||
return Array.from(state.feedIdByView[view] ?? []).map((feedId) => state.data[feedId])
|
||||
}
|
||||
|
||||
export const getFeedSubscriptionIdsSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return Array.from(state.feedIdByView[view])
|
||||
return Array.from(state.feedIdByView[view] ?? [])
|
||||
}
|
||||
|
||||
export const getAllFeedSubscriptionSelector = (state: StateType) => () => {
|
||||
|
|
@ -354,7 +356,7 @@ export const getCategoriesSelector = (state: StateType) => (view?: FeedViewType)
|
|||
? Array.from(
|
||||
new Set(Object.values(state.categories).flatMap((category) => Array.from(category))),
|
||||
)
|
||||
: Array.from(state.categories[view])
|
||||
: Array.from(state.categories[view] ?? [])
|
||||
}
|
||||
|
||||
export const getSubscriptionCategoryExistSelector =
|
||||
|
|
@ -364,7 +366,7 @@ export const getSubscriptionCategoryExistSelector =
|
|||
}
|
||||
|
||||
export const getCategoriesByViewSelector = (state: StateType) => (view: FeedViewType) => {
|
||||
return state.categories[view]
|
||||
return state.categories[view] ?? new Set<string>()
|
||||
}
|
||||
|
||||
export const getListSubscriptionCountSelector = (state: StateType) => () => {
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ export const useSubscriptionCategoryExist = (categoryId: string | undefined | nu
|
|||
|
||||
export const getSubscriptionCategory = (view?: FeedViewType) => {
|
||||
const state = useSubscriptionStore.getState()
|
||||
return view === undefined ? [] : Array.from(state.categories[view])
|
||||
return view === undefined ? [] : Array.from(state.categories[view] ?? [])
|
||||
}
|
||||
|
||||
export const useViewWithSubscription = () =>
|
||||
|
|
@ -269,7 +269,7 @@ export const useViewWithSubscription = () =>
|
|||
) {
|
||||
return true
|
||||
} else {
|
||||
return state.feedIdByView[view.view].size > 0
|
||||
return (state.feedIdByView[view.view]?.size ?? 0) > 0
|
||||
}
|
||||
})
|
||||
.map((v) => v.view)
|
||||
|
|
|
|||
|
|
@ -115,15 +115,15 @@ class SubscriptionActions implements Hydratable, Resetable {
|
|||
draft.subscriptionIdSet.add(subscriptionSetId)
|
||||
|
||||
if (subscription.feedId && subscription.type === "feed") {
|
||||
draft.feedIdByView[subscription.view].add(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All].add(subscription.feedId)
|
||||
draft.feedIdByView[subscription.view]!.add(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All]!.add(subscription.feedId)
|
||||
if (subscription.category) {
|
||||
draft.categories[subscription.view].add(subscription.category)
|
||||
draft.categories[subscription.view]!.add(subscription.category)
|
||||
}
|
||||
}
|
||||
if (subscription.listId && subscription.type === "list") {
|
||||
draft.listIdByView[subscription.view].add(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All].add(subscription.listId)
|
||||
draft.listIdByView[subscription.view]!.add(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All]!.add(subscription.listId)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -164,20 +164,21 @@ class SubscriptionActions implements Hydratable, Resetable {
|
|||
|
||||
toggleCategoryOpenState(view: FeedViewType, category: string) {
|
||||
immerSet((state) => {
|
||||
state.categoryOpenStateByView[view][category] = !state.categoryOpenStateByView[view][category]
|
||||
state.categoryOpenStateByView[view]![category] =
|
||||
!state.categoryOpenStateByView[view]![category]
|
||||
})
|
||||
}
|
||||
|
||||
changeCategoryOpenState(view: FeedViewType, category: string, status: boolean) {
|
||||
immerSet((state) => {
|
||||
state.categoryOpenStateByView[view][category] = status
|
||||
state.categoryOpenStateByView[view]![category] = status
|
||||
})
|
||||
}
|
||||
|
||||
expandCategoryOpenStateByView(view: FeedViewType, isOpen: boolean) {
|
||||
immerSet((state) => {
|
||||
for (const category in state.categoryOpenStateByView[view]) {
|
||||
state.categoryOpenStateByView[view][category] = isOpen
|
||||
for (const category in state.categoryOpenStateByView[view]!) {
|
||||
state.categoryOpenStateByView[view]![category] = isOpen
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -234,15 +235,15 @@ class SubscriptionSyncService {
|
|||
immerSet((draft) => {
|
||||
if (
|
||||
subscription.category &&
|
||||
!draft.categories[subscription.view].has(subscription.category)
|
||||
!draft.categories[subscription.view]!.has(subscription.category)
|
||||
) {
|
||||
addNewCategory = true
|
||||
draft.categories[subscription.view].add(subscription.category)
|
||||
draft.categories[subscription.view]!.add(subscription.category)
|
||||
}
|
||||
|
||||
if (subscription.type === "feed") {
|
||||
draft.feedIdByView[current.view].delete(current.feedId!)
|
||||
draft.feedIdByView[subscription.view].add(subscription.feedId!)
|
||||
draft.feedIdByView[current.view]!.delete(current.feedId!)
|
||||
draft.feedIdByView[subscription.view]!.add(subscription.feedId!)
|
||||
}
|
||||
|
||||
draft.data[subscriptionId] = subscription
|
||||
|
|
@ -251,12 +252,12 @@ class SubscriptionSyncService {
|
|||
tx.rollback((current) => {
|
||||
immerSet((draft) => {
|
||||
if (addNewCategory && subscription.category) {
|
||||
draft.categories[subscription.view].delete(subscription.category)
|
||||
draft.categories[subscription.view]!.delete(subscription.category)
|
||||
}
|
||||
|
||||
if (subscription.type === "feed") {
|
||||
draft.feedIdByView[subscription.view].delete(subscription.feedId!)
|
||||
draft.feedIdByView[current.view].add(current.feedId!)
|
||||
draft.feedIdByView[subscription.view]!.delete(subscription.feedId!)
|
||||
draft.feedIdByView[current.view]!.add(current.feedId!)
|
||||
}
|
||||
|
||||
draft.data[subscriptionId] = current
|
||||
|
|
@ -283,14 +284,14 @@ class SubscriptionSyncService {
|
|||
const data = await api().subscriptions.create(subscription)
|
||||
|
||||
if (data.feed) {
|
||||
feedActions.upsertMany([data.feed])
|
||||
feedActions.upsertMany([data.feed as any])
|
||||
tracker.subscribe({ feedId: data.feed.id, view: subscription.view })
|
||||
}
|
||||
|
||||
if (data.list) {
|
||||
listActions.upsertMany([
|
||||
{
|
||||
...data.list,
|
||||
...(data.list as any),
|
||||
userId: data.list.ownerUserId,
|
||||
type: "list",
|
||||
subscriptionCount: null,
|
||||
|
|
@ -343,16 +344,16 @@ class SubscriptionSyncService {
|
|||
if (!subscription) continue
|
||||
draft.subscriptionIdSet.delete(getSubscriptionDBId(subscription))
|
||||
if (subscription.feedId) {
|
||||
draft.feedIdByView[subscription.view].delete(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All].delete(subscription.feedId)
|
||||
draft.feedIdByView[subscription.view]!.delete(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All]!.delete(subscription.feedId)
|
||||
}
|
||||
if (subscription.listId) {
|
||||
draft.listIdByView[subscription.view].delete(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All].delete(subscription.listId)
|
||||
draft.listIdByView[subscription.view]!.delete(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All]!.delete(subscription.listId)
|
||||
}
|
||||
if (subscription.category) {
|
||||
draft.categories[subscription.view].delete(subscription.category)
|
||||
draft.categories[FeedViewType.All].delete(subscription.category)
|
||||
draft.categories[subscription.view]!.delete(subscription.category)
|
||||
draft.categories[FeedViewType.All]!.delete(subscription.category)
|
||||
}
|
||||
delete draft.data[id]
|
||||
}
|
||||
|
|
@ -377,16 +378,16 @@ class SubscriptionSyncService {
|
|||
|
||||
draft.subscriptionIdSet.add(getSubscriptionDBId(subscription))
|
||||
if (subscription.feedId) {
|
||||
draft.feedIdByView[subscription.view].add(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All].add(subscription.feedId)
|
||||
draft.feedIdByView[subscription.view]!.add(subscription.feedId)
|
||||
draft.feedIdByView[FeedViewType.All]!.add(subscription.feedId)
|
||||
}
|
||||
if (subscription.listId) {
|
||||
draft.listIdByView[subscription.view].add(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All].add(subscription.listId)
|
||||
draft.listIdByView[subscription.view]!.add(subscription.listId)
|
||||
draft.listIdByView[FeedViewType.All]!.add(subscription.listId)
|
||||
}
|
||||
if (subscription.category) {
|
||||
draft.categories[subscription.view].add(subscription.category)
|
||||
draft.categories[FeedViewType.All].add(subscription.category)
|
||||
draft.categories[subscription.view]!.add(subscription.category)
|
||||
draft.categories[FeedViewType.All]!.add(subscription.category)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -436,12 +437,12 @@ class SubscriptionSyncService {
|
|||
if (!subscription) continue
|
||||
|
||||
const currentView = subscription.view
|
||||
draft.feedIdByView[currentView].delete(feedId)
|
||||
draft.feedIdByView[newView].add(feedId)
|
||||
draft.feedIdByView[currentView]!.delete(feedId)
|
||||
draft.feedIdByView[newView]!.add(feedId)
|
||||
subscription.view = newView
|
||||
|
||||
if (newCategory) {
|
||||
draft.categories[newView].add(newCategory)
|
||||
draft.categories[newView]!.add(newCategory)
|
||||
subscription.category = newCategory
|
||||
}
|
||||
}
|
||||
|
|
@ -464,8 +465,8 @@ class SubscriptionSyncService {
|
|||
if (!current[index]) continue
|
||||
|
||||
subscription.view = current[index].view
|
||||
draft.feedIdByView[newView].delete(feedId)
|
||||
draft.feedIdByView[current[index].view].add(feedId)
|
||||
draft.feedIdByView[newView]!.delete(feedId)
|
||||
draft.feedIdByView[current[index]!.view]!.add(feedId)
|
||||
|
||||
if (newCategory) {
|
||||
const currentCategory = current[index].category
|
||||
|
|
@ -504,9 +505,9 @@ class SubscriptionSyncService {
|
|||
return
|
||||
}
|
||||
|
||||
draft.data[listId].view = newView
|
||||
draft.listIdByView[currentView].delete(listId)
|
||||
draft.listIdByView[newView].add(listId)
|
||||
draft.data[listId]!.view = newView
|
||||
draft.listIdByView[currentView]!.delete(listId)
|
||||
draft.listIdByView[newView]!.add(listId)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -523,9 +524,9 @@ class SubscriptionSyncService {
|
|||
return
|
||||
}
|
||||
|
||||
draft.data[listId].view = current.view
|
||||
draft.listIdByView[newView].delete(listId)
|
||||
draft.listIdByView[currentView].add(listId)
|
||||
draft.data[listId]!.view = current.view
|
||||
draft.listIdByView[newView]!.delete(listId)
|
||||
draft.listIdByView[currentView]!.add(listId)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -552,7 +553,7 @@ class SubscriptionSyncService {
|
|||
if (!subscription) continue
|
||||
subscription.category = null
|
||||
}
|
||||
draft.categories[view].delete(category)
|
||||
draft.categories[view]!.delete(category)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -571,7 +572,7 @@ class SubscriptionSyncService {
|
|||
subscription.category = category
|
||||
}
|
||||
|
||||
draft.categories[view].add(category)
|
||||
draft.categories[view]!.add(category)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -625,13 +626,13 @@ class SubscriptionSyncService {
|
|||
if (!subscription) continue
|
||||
subscription.category = newCategory
|
||||
}
|
||||
draft.categories[view].add(newCategory)
|
||||
draft.categories[view].delete(lastCategory)
|
||||
draft.categories[view]!.add(newCategory)
|
||||
draft.categories[view]!.delete(lastCategory)
|
||||
|
||||
const lastCategoryOpenState = draft.categoryOpenStateByView[view][lastCategory]
|
||||
const lastCategoryOpenState = draft.categoryOpenStateByView[view]![lastCategory]
|
||||
if (typeof lastCategoryOpenState === "boolean") {
|
||||
draft.categoryOpenStateByView[view][newCategory] = lastCategoryOpenState
|
||||
delete draft.categoryOpenStateByView[view][lastCategory]
|
||||
draft.categoryOpenStateByView[view]![newCategory] = lastCategoryOpenState
|
||||
delete draft.categoryOpenStateByView[view]![lastCategory]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -651,13 +652,13 @@ class SubscriptionSyncService {
|
|||
const defaultCategory = getDefaultCategory(subscription)
|
||||
subscription.category = lastCategory !== defaultCategory ? lastCategory : null
|
||||
}
|
||||
draft.categories[view].delete(newCategory)
|
||||
draft.categories[view].add(lastCategory)
|
||||
draft.categories[view]!.delete(newCategory)
|
||||
draft.categories[view]!.add(lastCategory)
|
||||
|
||||
const lastCategoryOpenState = draft.categoryOpenStateByView[view][newCategory]
|
||||
const lastCategoryOpenState = draft.categoryOpenStateByView[view]![newCategory]
|
||||
if (typeof lastCategoryOpenState === "boolean") {
|
||||
draft.categoryOpenStateByView[view][lastCategory] = lastCategoryOpenState
|
||||
delete draft.categoryOpenStateByView[view][newCategory]
|
||||
draft.categoryOpenStateByView[view]![lastCategory] = lastCategoryOpenState
|
||||
delete draft.categoryOpenStateByView[view]![newCategory]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ export const useEntryTranslation = ({
|
|||
useCallback(
|
||||
(state) => {
|
||||
if (!enabled && !actionSetting) return
|
||||
return state.data[entryId]?.[language]
|
||||
return state.data[entryId]?.[language as SupportedActionLanguage]
|
||||
},
|
||||
[actionSetting, entryId, language, enabled],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { UserRole } from "@follow/constants"
|
||||
import type { UserRole } from "@follow/constants"
|
||||
import type { UserSchema } from "@follow/database/schemas/types"
|
||||
import { UserService } from "@follow/database/services/user"
|
||||
import type { AuthUser } from "@follow-app/client-sdk"
|
||||
|
|
@ -51,10 +51,10 @@ class UserSyncService {
|
|||
immerSet((state) => {
|
||||
for (const user of usersArray) {
|
||||
state.users[user.id] = {
|
||||
...user,
|
||||
email: null,
|
||||
isMe: whoami?.id === user.id,
|
||||
...user,
|
||||
}
|
||||
} as UserModel
|
||||
}
|
||||
})
|
||||
return usersObject
|
||||
|
|
@ -97,7 +97,7 @@ class UserSyncService {
|
|||
state.rsshubSubscriptionLimit = res.rsshubSubscriptionLimit ?? null
|
||||
state.feedSubscriptionLimit = res.feedSubscriptionLimit ?? null
|
||||
})
|
||||
userActions.upsertMany([user])
|
||||
userActions.upsertMany([user as unknown as UserModel])
|
||||
|
||||
return res
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ class UserSyncService {
|
|||
...whoami,
|
||||
...data,
|
||||
}
|
||||
userActions.upsertMany([nextUser])
|
||||
userActions.upsertMany([nextUser as unknown as UserModel])
|
||||
})
|
||||
tx.rollback(() => {
|
||||
immerSet((state) => {
|
||||
|
|
@ -189,22 +189,11 @@ class UserSyncService {
|
|||
tx.persist(async () => {
|
||||
const { whoami } = get()
|
||||
if (!whoami) return
|
||||
userActions.upsertMany([{ ...whoami, email }])
|
||||
userActions.upsertMany([{ ...whoami, email } as unknown as UserModel])
|
||||
})
|
||||
await tx.run()
|
||||
}
|
||||
|
||||
async applyInvitationCode(code: string) {
|
||||
const res = await api().invitations.use({ code })
|
||||
if (res.code === 0) {
|
||||
immerSet((state) => {
|
||||
state.role = UserRole.Pro
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
async fetchUser(userId: string | undefined) {
|
||||
if (!userId) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ class APIMorph {
|
|||
feedId: item.feeds.id,
|
||||
inboxHandle: item.feeds.type === "inbox" ? item.feeds.id : null,
|
||||
read: item.read,
|
||||
sources: "from" in item ? (item.from ?? null) : null,
|
||||
sources: "from" in item && Array.isArray(item.from) ? item.from : null,
|
||||
settings: item.settings ?? null,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@ import { trackManager } from "./track-manager"
|
|||
export class TrackerPoints {
|
||||
// App
|
||||
identify(props: AuthUser) {
|
||||
this.manager.identify(props)
|
||||
this.manager.identify({
|
||||
id: props.id,
|
||||
name: props.name,
|
||||
email: props.email,
|
||||
image: props.image,
|
||||
handle: props.handle,
|
||||
})
|
||||
this.track(TrackerMapper.Identify, {
|
||||
id: props.id,
|
||||
name: props.name,
|
||||
|
|
|
|||
841
pnpm-lock.yaml
841
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -8,8 +8,8 @@ packages:
|
|||
- "!**/example/**"
|
||||
|
||||
catalog:
|
||||
"@follow-app/client-sdk": 0.3.92
|
||||
"@folo-services/ai-tools": 0.2.65
|
||||
"@follow-app/client-sdk": 0.3.95
|
||||
"@folo-services/ai-tools": 0.2.66
|
||||
tailwindcss-uikit-colors: 1.0.0
|
||||
typescript: 5.9.3
|
||||
|
||||
|
|
@ -40,6 +40,8 @@ overrides:
|
|||
"@floating-ui/dom": 1.7.2
|
||||
"@floating-ui/react": 0.27.14
|
||||
"@floating-ui/react-dom": 2.1.4
|
||||
"@follow-app/client-sdk": 0.3.95
|
||||
"@folo-services/drizzle": 0.1.47
|
||||
"@react-native-menu/menu": 2.0.0
|
||||
"@types/react": 19.1.17
|
||||
array-includes: npm:@nolyfill/array-includes@latest
|
||||
|
|
|
|||
Loading…
Reference in New Issue