1+ import {
2+ useMutation ,
3+ useQueryClient ,
4+ useSuspenseQuery ,
5+ } from "@tanstack/react-query" ;
16import { PackagePlus , Star } from "lucide-react" ;
2- import type { MouseEvent , PropsWithChildren } from "react" ;
7+ import type { ComponentProps , MouseEvent , PropsWithChildren } from "react" ;
38import { useCallback , useMemo , useState } from "react" ;
49
510import { ConfirmationDialog } from "@/components/shared/Dialogs" ;
611import { Button } from "@/components/ui/button" ;
712import { Icon } from "@/components/ui/icon" ;
13+ import { Spinner } from "@/components/ui/spinner" ;
14+ import { useGuaranteedHydrateComponentReference } from "@/hooks/useHydrateComponentReference" ;
815import { cn } from "@/lib/utils" ;
916import { useComponentLibrary } from "@/providers/ComponentLibraryProvider" ;
1017import { hydrateComponentReference } from "@/services/componentService" ;
1118import type { ComponentReference } from "@/utils/componentSpec" ;
1219import { getComponentName } from "@/utils/getComponentName" ;
20+ import { getComponentById } from "@/utils/localforage" ;
21+
22+ import { withSuspenseWrapper } from "./SuspenseWrapper" ;
1323
1424interface ComponentFavoriteToggleProps {
1525 component : ComponentReference ;
1626 hideDelete ?: boolean ;
1727}
1828
19- interface StateButtonProps {
29+ interface StateButtonProps extends ComponentProps < typeof Button > {
2030 active ?: boolean ;
2131 isDanger ?: boolean ;
2232 onClick ?: ( ) => void ;
@@ -27,6 +37,7 @@ const IconStateButton = ({
2737 isDanger = false ,
2838 onClick,
2939 children,
40+ ...props
3041} : PropsWithChildren < StateButtonProps > ) => {
3142 const handleFavorite = useCallback (
3243 ( e : MouseEvent ) => {
@@ -51,6 +62,7 @@ const IconStateButton = ({
5162 ) }
5263 variant = "ghost"
5364 size = "icon"
65+ { ...props }
5466 >
5567 { children }
5668 </ Button >
@@ -84,28 +96,59 @@ const DeleteFromLibraryButton = ({ active, onClick }: StateButtonProps) => {
8496 ) ;
8597} ;
8698
99+ const favoriteComponentKey = ( component : ComponentReference ) => {
100+ return [ "component" , "is-favorite" , component . digest ] ;
101+ } ;
102+
103+ const FavoriteToggleButton = withSuspenseWrapper (
104+ ( { component } : { component : ComponentReference } ) => {
105+ const queryClient = useQueryClient ( ) ;
106+
107+ const { setComponentFavorite } = useComponentLibrary ( ) ;
108+ const hydratedComponent = useGuaranteedHydrateComponentReference ( component ) ;
109+
110+ const { data : isFavorited } = useSuspenseQuery ( {
111+ queryKey : favoriteComponentKey ( component ) ,
112+ queryFn : async ( ) =>
113+ ( await getComponentById ( `component-${ hydratedComponent . digest } ` ) )
114+ ?. favorited ?? false ,
115+ } ) ;
116+
117+ const { mutate : setFavorite } = useMutation ( {
118+ mutationFn : async ( ) =>
119+ setComponentFavorite ( hydratedComponent , ! isFavorited ) ,
120+ onSuccess : ( ) => {
121+ queryClient . invalidateQueries ( {
122+ queryKey : favoriteComponentKey ( hydratedComponent ) ,
123+ } ) ;
124+ } ,
125+ } ) ;
126+
127+ return < FavoriteStarButton active = { isFavorited } onClick = { setFavorite } /> ;
128+ } ,
129+ ( ) => < Spinner size = { 10 } /> ,
130+ ( ) => (
131+ < IconStateButton disabled >
132+ < Icon name = "Star" />
133+ </ IconStateButton >
134+ ) ,
135+ ) ;
136+
87137export const ComponentFavoriteToggle = ( {
88138 component,
89139 hideDelete = false ,
90140} : ComponentFavoriteToggleProps ) => {
91141 const {
92142 addToComponentLibrary,
93143 removeFromComponentLibrary,
94- checkIfFavorited,
95144 checkIfUserComponent,
96145 checkLibraryContainsComponent,
97- setComponentFavorite,
98146 } = useComponentLibrary ( ) ;
99147
100148 const [ isOpen , setIsOpen ] = useState ( false ) ;
101149
102150 const { spec, url } = component ;
103151
104- const isFavorited = useMemo (
105- ( ) => checkIfFavorited ( component ) ,
106- [ component , checkIfFavorited ] ,
107- ) ;
108-
109152 const isUserComponent = useMemo (
110153 ( ) => checkIfUserComponent ( component ) ,
111154 [ component , checkIfUserComponent ] ,
@@ -121,10 +164,6 @@ export const ComponentFavoriteToggle = ({
121164 [ spec , url ] ,
122165 ) ;
123166
124- const onFavorite = useCallback ( ( ) => {
125- setComponentFavorite ( component , ! isFavorited ) ;
126- } , [ isFavorited , setComponentFavorite ] ) ;
127-
128167 // Delete User Components
129168 const handleDelete = useCallback ( async ( ) => {
130169 removeFromComponentLibrary ( component ) ;
@@ -166,7 +205,7 @@ export const ComponentFavoriteToggle = ({
166205 { ! isInLibrary && < AddToLibraryButton onClick = { openConfirmationDialog } /> }
167206
168207 { isInLibrary && ! isUserComponent && (
169- < FavoriteStarButton active = { isFavorited } onClick = { onFavorite } />
208+ < FavoriteToggleButton component = { component } />
170209 ) }
171210
172211 { showDeleteButton && (
0 commit comments