ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

allow opt-out of data save

+78 -22
+23 -1
netlify/functions/save-results.ts
··· 35 35 uploadId: string; 36 36 sourcePlatform: string; 37 37 results: SearchResult[]; 38 + saveData?: boolean; 38 39 } 39 40 40 41 export const handler: Handler = async ( ··· 75 76 76 77 // Parse request body 77 78 const body: SaveResultsRequest = JSON.parse(event.body || "{}"); 78 - const { uploadId, sourcePlatform, results } = body; 79 + const { uploadId, sourcePlatform, results, saveData } = body; 79 80 80 81 if (!uploadId || !sourcePlatform || !Array.isArray(results)) { 81 82 return { ··· 83 84 headers: { "Content-Type": "application/json" }, 84 85 body: JSON.stringify({ 85 86 error: "uploadId, sourcePlatform, and results are required", 87 + }), 88 + }; 89 + } 90 + 91 + // Server-side validation for saveData flag, controlled by frontend 92 + if (saveData === false) { 93 + console.log( 94 + `User ${userSession.did} has data storage disabled - skipping save`, 95 + ); 96 + return { 97 + statusCode: 200, 98 + headers: { "Content-Type": "application/json" }, 99 + body: JSON.stringify({ 100 + success: true, 101 + message: "Data storage disabled - results not saved", 102 + uploadId, 103 + totalUsers: results.length, 104 + matchedUsers: results.filter((r) => r.atprotoMatches.length > 0) 105 + .length, 106 + unmatchedUsers: results.filter((r) => r.atprotoMatches.length === 0) 107 + .length, 86 108 }), 87 109 }; 88 110 }
+24 -17
src/App.tsx
··· 83 83 84 84 searchAllUsers(initialResults, setStatusMessage, () => { 85 85 setCurrentStep("results"); 86 - // Prevent duplicate saves 87 - if (saveCalledRef.current !== uploadId) { 88 - saveCalledRef.current = uploadId; 89 - // Need to wait for React to finish updating searchResults state 90 - // Use a longer delay and access via setSearchResults callback to get final state 91 - setTimeout(() => { 92 - setSearchResults((currentResults) => { 93 - if (currentResults.length > 0) { 94 - apiClient 95 - .saveResults(uploadId, platform, currentResults) 96 - .catch((err) => { 97 - console.error("Background save failed:", err); 98 - }); 99 - } 100 - return currentResults; // Don't modify, just return as-is 101 - }); 102 - }, 1000); // Longer delay to ensure all state updates complete 86 + 87 + // CONDITIONAL SAVE: Only save if user has enabled data storage 88 + if (userSettings.saveData) { 89 + // Prevent duplicate saves 90 + if (saveCalledRef.current !== uploadId) { 91 + saveCalledRef.current = uploadId; 92 + // Need to wait for React to finish updating searchResults state 93 + // Use a longer delay and access via setSearchResults callback to get final state 94 + setTimeout(() => { 95 + setSearchResults((currentResults) => { 96 + if (currentResults.length > 0) { 97 + apiClient 98 + .saveResults(uploadId, platform, currentResults) 99 + .catch((err) => { 100 + console.error("Background save failed:", err); 101 + }); 102 + } 103 + return currentResults; // Don't modify, just return as-is 104 + }); 105 + }, 1000); // Longer delay to ensure all state updates complete 106 + } 107 + } else { 108 + console.log("Data storage disabled - skipping save to database"); 103 109 } 104 110 }); 105 111 }, 106 112 setStatusMessage, 113 + userSettings, // Pass userSettings to hook 107 114 ); 108 115 109 116 // Load previous upload handler
+21 -3
src/components/HistoryTab.tsx
··· 1 - import { Upload, Sparkles, ChevronRight } from "lucide-react"; 1 + import { Upload, Sparkles, ChevronRight, Database } from "lucide-react"; 2 2 import { ATPROTO_APPS } from "../constants/atprotoApps"; 3 3 import type { Upload as UploadType } from "../types"; 4 4 import type { UserSettings } from "../types/settings"; ··· 77 77 </div> 78 78 </div> 79 79 80 + {/* Data Storage Disabled Notice */} 81 + {!userSettings.saveData && ( 82 + <div className="mb-4 p-4 border-2 rounded-xl border-orange-650/50 dark:border-amber-400/50 bg-purple-100/50 dark:bg-slate-900/50"> 83 + <div className="flex items-start space-x-3"> 84 + <Database className="w-5 h-5 text-orange-600 dark:text-amber-400 flex-shrink-0 mt-0.5" /> 85 + <div> 86 + <h3 className="font-semibold text-purple-950 dark:text-cyan-50 mb-1"> 87 + Data Storage Disabled 88 + </h3> 89 + <p className="text-sm text-purple-900 dark:text-cyan-100"> 90 + You've disabled data storage in your settings. Enable "Save my 91 + data" in the Settings tab to save your upload history. 92 + </p> 93 + </div> 94 + </div> 95 + </div> 96 + )} 97 + 80 98 {isLoading ? ( 81 99 <div className="space-y-6"> 82 100 {[...Array(3)].map((_, i) => ( ··· 94 112 </div> 95 113 ) : uploads.length === 0 ? ( 96 114 <div className="text-center py-12"> 97 - <Upload className="w-16 h-16 text-purple-300 dark:text-slate-600 mx-auto mb-4" /> 115 + <Upload className="w-16 h-16 text-purple-900 dark:text-cyan-100 mx-auto mb-4" /> 98 116 <p className="text-purple-750 dark:text-cyan-250 font-medium"> 99 117 No previous uploads yet 100 118 </p> 101 - <p className="text-sm text-purple-750/70 dark:text-cyan-250/70 mt-2"> 119 + <p className="text-sm text-purple-950 dark:text-cyan-50 mt-2"> 102 120 Upload your first file to get started 103 121 </p> 104 122 </div>
+2 -1
src/hooks/useFileUpload.ts
··· 1 1 import { parseDataFile } from "../lib/fileExtractor"; 2 - import type { SearchResult } from "../types"; 2 + import type { SearchResult, UserSettings } from "../types"; 3 3 4 4 export function useFileUpload( 5 5 onSearchStart: (results: SearchResult[], platform: string) => void, 6 6 onStatusUpdate: (message: string) => void, 7 + userSettings: UserSettings, 7 8 ) { 8 9 async function handleFileUpload( 9 10 e: React.ChangeEvent<HTMLInputElement>,
+8
src/types/index.ts
··· 81 81 matchedUsers: number; 82 82 unmatchedUsers: number; 83 83 } 84 + 85 + // Re-export settings types for convenience 86 + export type { 87 + UserSettings, 88 + PlatformDestinations, 89 + AtprotoApp, 90 + AtprotoAppId, 91 + } from "./settings";