A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: invalidate dashboard & login submit

+114 -3
+5
apps/mobile/app/(tabs)/search.tsx
··· 34 34 import { useAuth } from "@/contexts/auth"; 35 35 import { useTheme } from "@/contexts/theme"; 36 36 import { useToast } from "@/contexts/toast"; 37 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 37 38 import { createTitleSlug } from "@/lib/utils"; 38 39 39 40 const DEBOUNCE_MS = 300; ··· 213 214 path: { userDid: user?.did || "" }, 214 215 }), 215 216 }); 217 + invalidateUserShelfQueries(queryClient, user?.did); 216 218 showToast("Added to your shelf", "success"); 217 219 }, 218 220 onError: () => { ··· 229 231 path: { userDid: user?.did || "" }, 230 232 }), 231 233 }); 234 + invalidateUserShelfQueries(queryClient, user?.did); 232 235 showToast("Removed from your shelf", "success"); 233 236 }, 234 237 onError: () => { ··· 245 248 path: { userDid: user?.did || "" }, 246 249 }), 247 250 }); 251 + invalidateUserShelfQueries(queryClient, user?.did); 248 252 showToast("Added to your shelf", "success"); 249 253 }, 250 254 onError: () => { ··· 261 265 path: { userDid: user?.did || "" }, 262 266 }), 263 267 }); 268 + invalidateUserShelfQueries(queryClient, user?.did); 264 269 showToast("Removed from your shelf", "success"); 265 270 }, 266 271 onError: () => {
+9 -3
apps/mobile/app/login.tsx
··· 96 96 } 97 97 }; 98 98 99 + const handleConnect = () => { 100 + if (isSubmitting) return; 101 + void startLogin(handle.trim() || undefined); 102 + }; 103 + 99 104 const handleSignup = async () => { 100 105 setIsSubmitting(true); 101 106 posthog.capture("user_signed_up", { ··· 251 256 containerStyle={{ width: "100%" }} 252 257 value={handle} 253 258 onChangeText={setHandle} 259 + onSubmitEditing={handleConnect} 254 260 placeholder="alice.example.com" 255 261 autoCapitalize="none" 256 262 autoCorrect={false} 263 + returnKeyType="go" 264 + enablesReturnKeyAutomatically 257 265 editable={!isSubmitting} 258 266 variant="outlined" 259 267 /> ··· 324 332 borderRadius: 10, 325 333 opacity: isSubmitting ? 0.7 : 1, 326 334 }} 327 - onPress={() => { 328 - void startLogin(handle || undefined); 329 - }} 335 + onPress={handleConnect} 330 336 disabled={isSubmitting} 331 337 activeOpacity={0.8} 332 338 >
+4
apps/mobile/app/movie/[id].tsx
··· 47 47 import { borderRadius, spacing } from "@/constants/spacing"; 48 48 import { useTheme } from "@/contexts/theme"; 49 49 import { useToast } from "@/contexts/toast"; 50 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 50 51 51 52 const POSTER_BASE_URL = "https://image.tmdb.org/t/p/w500"; 52 53 const BACKDROP_BASE_URL = "https://image.tmdb.org/t/p/w1280"; ··· 229 230 path: { userDid: user?.did || "" }, 230 231 }), 231 232 }); 233 + invalidateUserShelfQueries(queryClient, user?.did); 232 234 queryClient.invalidateQueries({ 233 235 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 234 236 path: { userDid: user?.did || "", movieId }, ··· 258 260 path: { userDid: user?.did || "" }, 259 261 }), 260 262 }); 263 + invalidateUserShelfQueries(queryClient, user?.did); 261 264 queryClient.invalidateQueries({ 262 265 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 263 266 path: { userDid: user?.did || "", movieId }, ··· 283 286 path: { userDid: user?.did || "" }, 284 287 }), 285 288 }); 289 + invalidateUserShelfQueries(queryClient, user?.did); 286 290 queryClient.invalidateQueries({ 287 291 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 288 292 path: { userDid: user?.did || "", movieId },
+3
apps/mobile/app/show/[id].tsx
··· 41 41 import { borderRadius, spacing } from "@/constants/spacing"; 42 42 import { useTheme } from "@/contexts/theme"; 43 43 import { useToast } from "@/contexts/toast"; 44 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 44 45 import { 45 46 getTmdbBackdropUrl, 46 47 getTmdbPosterUrl, ··· 168 169 path: { userDid: user?.did || "" }, 169 170 }), 170 171 }); 172 + invalidateUserShelfQueries(queryClient, user?.did); 171 173 queryClient.invalidateQueries({ 172 174 queryKey: ["showsControllerGetShowWatchHistory"], 173 175 }); ··· 207 209 path: { userDid: user?.did || "" }, 208 210 }), 209 211 }); 212 + invalidateUserShelfQueries(queryClient, user?.did); 210 213 queryClient.invalidateQueries({ 211 214 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 212 215 path: { userDid: user?.did || "", showId: id },
+4
apps/mobile/app/show/[id]/season/[seasonNumber]/episode/[episodeNumber]/index.tsx
··· 51 51 import { borderRadius, spacing } from "@/constants/spacing"; 52 52 import { useTheme } from "@/contexts/theme"; 53 53 import { useToast } from "@/contexts/toast"; 54 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 54 55 import { 55 56 buildScopedShowMediaId, 56 57 getTmdbBackdropUrl, ··· 273 274 path: { userDid: resolvedUserDid }, 274 275 }), 275 276 }); 277 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 276 278 queryClient.invalidateQueries({ 277 279 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 278 280 path: { userDid: resolvedUserDid, showId: id }, ··· 295 297 path: { userDid: resolvedUserDid }, 296 298 }), 297 299 }); 300 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 298 301 queryClient.invalidateQueries({ 299 302 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 300 303 path: { userDid: resolvedUserDid, showId: id }, ··· 316 319 path: { userDid: resolvedUserDid }, 317 320 }), 318 321 }); 322 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 319 323 queryClient.invalidateQueries({ 320 324 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 321 325 path: { userDid: resolvedUserDid, showId: id },
+3
apps/mobile/app/show/[id]/season/[seasonNumber]/index.tsx
··· 44 44 import { borderRadius, spacing } from "@/constants/spacing"; 45 45 import { useTheme } from "@/contexts/theme"; 46 46 import { useToast } from "@/contexts/toast"; 47 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 47 48 import { 48 49 buildScopedShowMediaId, 49 50 getTmdbBackdropUrl, ··· 190 191 path: { userDid: resolvedUserDid }, 191 192 }), 192 193 }); 194 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 193 195 queryClient.invalidateQueries({ 194 196 queryKey: ["showsControllerGetShowWatchHistory"], 195 197 }); ··· 228 230 path: { userDid: resolvedUserDid }, 229 231 }), 230 232 }); 233 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 231 234 queryClient.invalidateQueries({ 232 235 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 233 236 path: { userDid: resolvedUserDid, showId: id },
apps/mobile/build-1772371733639.aab

This is a binary file and will not be displayed.

apps/mobile/build-1772372004074.ipa

This is a binary file and will not be displayed.

+3
apps/mobile/components/detail/EpisodeCard.tsx
··· 13 13 import { borderRadius, spacing } from "@/constants/spacing"; 14 14 import { useTheme } from "@/contexts/theme"; 15 15 import { useToast } from "@/contexts/toast"; 16 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 16 17 17 18 const STILL_BASE_URL = "https://image.tmdb.org/t/p/w300"; 18 19 ··· 69 70 path: { userDid, showId }, 70 71 }), 71 72 }); 73 + invalidateUserShelfQueries(queryClient, userDid); 72 74 } 73 75 showToast("Episode marked watched"); 74 76 }, ··· 92 94 path: { userDid, showId }, 93 95 }), 94 96 }); 97 + invalidateUserShelfQueries(queryClient, userDid); 95 98 } 96 99 showToast("Removed from your shelf"); 97 100 },
+3
apps/mobile/components/detail/SeasonCard.tsx
··· 19 19 import { borderRadius, spacing } from "@/constants/spacing"; 20 20 import { useTheme } from "@/contexts/theme"; 21 21 import { useToast } from "@/contexts/toast"; 22 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 22 23 23 24 const POSTER_BASE_URL = "https://image.tmdb.org/t/p/w500"; 24 25 ··· 69 70 path: { userDid, showId }, 70 71 }), 71 72 }); 73 + invalidateUserShelfQueries(queryClient, userDid); 72 74 } 73 75 showToast(`Marked ${data.count} episodes as watched`); 74 76 }, ··· 92 94 path: { userDid, showId }, 93 95 }), 94 96 }); 97 + invalidateUserShelfQueries(queryClient, userDid); 95 98 } 96 99 showToast("Removed season from your shelf"); 97 100 },
+24
apps/mobile/lib/invalidate-shelf.ts
··· 1 + import type { QueryClient } from "@tanstack/react-query"; 2 + 3 + type QueryKeyRoot = { 4 + _id?: string; 5 + path?: { 6 + userDid?: string; 7 + }; 8 + }; 9 + 10 + export function invalidateUserShelfQueries( 11 + queryClient: QueryClient, 12 + userDid?: string, 13 + ) { 14 + if (!userDid) return; 15 + 16 + queryClient.invalidateQueries({ 17 + predicate: (query) => { 18 + const key = query.queryKey[0] as QueryKeyRoot | undefined; 19 + return ( 20 + key?._id === "shelfControllerGetUserShelf" && key.path?.userDid === userDid 21 + ); 22 + }, 23 + }); 24 + }
+5
apps/web/src/components/DatePickerModal.tsx
··· 16 16 import { M3Button } from "@/components/ui/m3-button"; 17 17 import { MaterialDatePicker } from "@/components/ui/material-date-picker"; 18 18 import { TimePicker } from "@/components/ui/time-picker"; 19 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 19 20 20 21 type DatePickerModalProps = { 21 22 open: boolean; ··· 80 81 path: { userDid: userDid || "" }, 81 82 }), 82 83 }); 84 + invalidateUserShelfQueries(queryClient, userDid); 83 85 if (isMovieMode) { 84 86 queryClient.invalidateQueries({ 85 87 queryKey: ["watchHistory", userDid, target.movieId], ··· 108 110 path: { userDid: userDid || "" }, 109 111 }), 110 112 }); 113 + invalidateUserShelfQueries(queryClient, userDid); 111 114 queryClient.invalidateQueries({ 112 115 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 113 116 path: { userDid: userDid || "", showId: target.showId }, ··· 137 140 path: { userDid: userDid || "" }, 138 141 }), 139 142 }); 143 + invalidateUserShelfQueries(queryClient, userDid); 140 144 queryClient.invalidateQueries({ 141 145 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 142 146 path: { userDid: userDid || "", showId: target.showId }, ··· 160 164 path: { userDid: userDid || "" }, 161 165 }), 162 166 }); 167 + invalidateUserShelfQueries(queryClient, userDid); 163 168 queryClient.invalidateQueries({ 164 169 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 165 170 path: { userDid: userDid || "", showId: target.showId },
+3
apps/web/src/components/MovieCard.tsx
··· 15 15 TooltipProvider, 16 16 TooltipTrigger, 17 17 } from "@/components/ui/tooltip"; 18 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 18 19 import { createTitleSlug, getTmdbPosterUrl } from "@/lib/utils"; 19 20 20 21 export interface MovieCardData { ··· 50 51 path: { userDid: user?.did || "" }, 51 52 }), 52 53 }); 54 + invalidateUserShelfQueries(queryClient, user?.did); 53 55 toast.success("Added to your shelf"); 54 56 }, 55 57 onError: () => { ··· 66 68 path: { userDid: user?.did || "" }, 67 69 }), 68 70 }); 71 + invalidateUserShelfQueries(queryClient, user?.did); 69 72 toast.success("Removed from your shelf"); 70 73 }, 71 74 onError: () => {
+3
apps/web/src/components/ShowCard.tsx
··· 16 16 TooltipProvider, 17 17 TooltipTrigger, 18 18 } from "@/components/ui/tooltip"; 19 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 19 20 import { createTitleSlug, getTmdbPosterUrl } from "@/lib/utils"; 20 21 21 22 interface ShowCardProps { ··· 43 44 path: { userDid: user?.did || "" }, 44 45 }), 45 46 }); 47 + invalidateUserShelfQueries(queryClient, user?.did); 46 48 toast.success("Added to your shelf"); 47 49 }, 48 50 onError: () => { ··· 59 61 path: { userDid: user?.did || "" }, 60 62 }), 61 63 }); 64 + invalidateUserShelfQueries(queryClient, user?.did); 62 65 toast.success("Removed from your shelf"); 63 66 }, 64 67 onError: () => {
+3
apps/web/src/components/detail/EpisodeCard.tsx
··· 8 8 import { Link } from "@tanstack/react-router"; 9 9 import { Calendar, Loader2, Plus, Star, Trash2 } from "lucide-react"; 10 10 import { toast } from "sonner"; 11 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 11 12 import { formatDateOnly } from "@/lib/utils"; 12 13 import type { ColorTheme, EpisodeSummary } from "./types"; 13 14 ··· 53 54 path: { userDid, showId }, 54 55 }), 55 56 }); 57 + invalidateUserShelfQueries(queryClient, userDid); 56 58 } 57 59 toast.success("Episode marked watched"); 58 60 }, ··· 82 84 path: { userDid, showId }, 83 85 }), 84 86 }); 87 + invalidateUserShelfQueries(queryClient, userDid); 85 88 } 86 89 toast.success("Removed from your shelf"); 87 90 },
+3
apps/web/src/components/detail/SeasonCard.tsx
··· 8 8 import { Link } from "@tanstack/react-router"; 9 9 import { Calendar, Film, Loader2, Plus, Trash2 } from "lucide-react"; 10 10 import { toast } from "sonner"; 11 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 11 12 import type { ColorTheme } from "./types"; 12 13 13 14 type SeasonCardProps = { ··· 63 64 path: { userDid, showId }, 64 65 }), 65 66 }); 67 + invalidateUserShelfQueries(queryClient, userDid); 66 68 } 67 69 toast.success(`Marked ${data.count} episodes as watched`); 68 70 }, ··· 92 94 path: { userDid, showId }, 93 95 }), 94 96 }); 97 + invalidateUserShelfQueries(queryClient, userDid); 95 98 } 96 99 toast.success("Removed season from your shelf"); 97 100 },
+25
apps/web/src/lib/invalidate-shelf.ts
··· 1 + import type { QueryClient } from "@tanstack/react-query"; 2 + 3 + type QueryKeyRoot = { 4 + _id?: string; 5 + path?: { 6 + userDid?: string; 7 + }; 8 + }; 9 + 10 + export function invalidateUserShelfQueries( 11 + queryClient: QueryClient, 12 + userDid?: string, 13 + ) { 14 + if (!userDid) return; 15 + 16 + queryClient.invalidateQueries({ 17 + predicate: (query) => { 18 + const key = query.queryKey[0] as QueryKeyRoot | undefined; 19 + return ( 20 + key?._id === "shelfControllerGetUserShelf" && 21 + key.path?.userDid === userDid 22 + ); 23 + }, 24 + }); 25 + }
+4
apps/web/src/routes/movies.$movieId.$title.tsx
··· 39 39 DialogTitle, 40 40 } from "@/components/ui/dialog"; 41 41 import { M3Button } from "@/components/ui/m3-button"; 42 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 42 43 import { 43 44 formatDateOnly, 44 45 formatDateWithTimezone, ··· 193 194 path: { userDid: user?.did || "" }, 194 195 }), 195 196 }); 197 + invalidateUserShelfQueries(queryClient, user?.did); 196 198 queryClient.invalidateQueries({ 197 199 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 198 200 path: { userDid: user?.did || "", movieId }, ··· 222 224 path: { userDid: user?.did || "" }, 223 225 }), 224 226 }); 227 + invalidateUserShelfQueries(queryClient, user?.did); 225 228 queryClient.invalidateQueries({ 226 229 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 227 230 path: { userDid: user?.did || "", movieId }, ··· 247 250 path: { userDid: user?.did || "" }, 248 251 }), 249 252 }); 253 + invalidateUserShelfQueries(queryClient, user?.did); 250 254 queryClient.invalidateQueries({ 251 255 queryKey: moviesControllerGetMovieWatchHistoryQueryKey({ 252 256 path: { userDid: user?.did || "", movieId },
+4
apps/web/src/routes/shows.$showId.$title.seasons.$seasonNumber.episodes.$episodeNumber.tsx
··· 39 39 DialogTitle, 40 40 } from "@/components/ui/dialog"; 41 41 import { M3Button } from "@/components/ui/m3-button"; 42 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 42 43 import { 43 44 buildScopedShowMediaId, 44 45 formatDateOnly, ··· 233 234 path: { userDid: resolvedUserDid }, 234 235 }), 235 236 }); 237 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 236 238 queryClient.invalidateQueries({ 237 239 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 238 240 path: { userDid: resolvedUserDid, showId }, ··· 253 255 path: { userDid: resolvedUserDid }, 254 256 }), 255 257 }); 258 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 256 259 queryClient.invalidateQueries({ 257 260 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 258 261 path: { userDid: resolvedUserDid, showId }, ··· 279 282 path: { userDid: resolvedUserDid }, 280 283 }), 281 284 }); 285 + invalidateUserShelfQueries(queryClient, resolvedUserDid); 282 286 queryClient.invalidateQueries({ 283 287 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 284 288 path: { userDid: resolvedUserDid, showId },
+3
apps/web/src/routes/shows.$showId.$title.seasons.$seasonNumber.tsx
··· 35 35 } from "@/components/detail"; 36 36 import { GenresSection } from "@/components/GenresSection"; 37 37 import { useTheme } from "@/components/theme-provider"; 38 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 38 39 import { 39 40 buildScopedShowMediaId, 40 41 formatDateOnly, ··· 194 195 path: { userDid: user?.did || "" }, 195 196 }), 196 197 }); 198 + invalidateUserShelfQueries(queryClient, user?.did); 197 199 queryClient.invalidateQueries({ 198 200 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 199 201 path: { userDid: user?.did || "", showId }, ··· 221 223 path: { userDid: user?.did || "" }, 222 224 }), 223 225 }); 226 + invalidateUserShelfQueries(queryClient, user?.did); 224 227 queryClient.invalidateQueries({ 225 228 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 226 229 path: { userDid: user?.did || "", showId },
+3
apps/web/src/routes/shows.$showId.$title.tsx
··· 33 33 } from "@/components/detail"; 34 34 import { GenresSection } from "@/components/GenresSection"; 35 35 import { useTheme } from "@/components/theme-provider"; 36 + import { invalidateUserShelfQueries } from "@/lib/invalidate-shelf"; 36 37 import { 37 38 formatDateOnly, 38 39 getTmdbBackdropUrl, ··· 156 157 path: { userDid: user?.did || "" }, 157 158 }), 158 159 }); 160 + invalidateUserShelfQueries(queryClient, user?.did); 159 161 queryClient.invalidateQueries({ 160 162 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 161 163 path: { userDid: user?.did || "", showId }, ··· 183 185 path: { userDid: user?.did || "" }, 184 186 }), 185 187 }); 188 + invalidateUserShelfQueries(queryClient, user?.did); 186 189 queryClient.invalidateQueries({ 187 190 queryKey: showsControllerGetShowWatchHistoryQueryKey({ 188 191 path: { userDid: user?.did || "", showId },