Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

finally type musicbrainz responses and functions

authored by Natalie and committed by Natalie B. 2c8c0290 6cebe2c4

Changed files
+84 -23
apps
amethyst
app
(tabs)
+84 -23
apps/amethyst/app/(tabs)/stamp.tsx
··· 5 5 TouchableOpacity, 6 6 FlatList, 7 7 Image, 8 - Alert, 9 8 Modal, 10 9 } from "react-native"; 11 10 import { useState } from "react"; ··· 18 17 import { Link, Stack } from "expo-router"; 19 18 import React from "react"; 20 19 21 - async function searchMusicbrainz(searchParams: { 20 + // MusicBrainz API Types 21 + interface MusicBrainzArtistCredit { 22 + artist: { 23 + id: string; 24 + name: string; 25 + "sort-name"?: string; 26 + }; 27 + joinphrase?: string; 28 + name: string; 29 + } 30 + 31 + interface MusicBrainzRelease { 32 + id: string; 33 + title: string; 34 + status?: string; 35 + date?: string; 36 + country?: string; 37 + disambiguation?: string; 38 + "track-count"?: number; 39 + } 40 + 41 + interface MusicBrainzRecording { 42 + id: string; 43 + title: string; 44 + length?: number; 45 + isrcs?: string[]; 46 + "artist-credit"?: MusicBrainzArtistCredit[]; 47 + releases?: MusicBrainzRelease[]; 48 + selectedRelease?: MusicBrainzRelease; // Added for UI state 49 + } 50 + 51 + interface SearchParams { 22 52 track?: string; 23 53 artist?: string; 24 - }) { 54 + release?: string; 55 + } 56 + 57 + interface SearchResultProps { 58 + result: MusicBrainzRecording; 59 + onSelectTrack: (track: MusicBrainzRecording | null) => void; 60 + isSelected: boolean; 61 + selectedRelease: MusicBrainzRelease | null; 62 + onReleaseSelect: (trackId: string, release: MusicBrainzRelease) => void; 63 + } 64 + 65 + interface PlayRecord { 66 + trackName: string; 67 + recordingMbId?: string; 68 + duration?: number; 69 + artistName: string; 70 + artistMbIds?: string[]; 71 + releaseName?: string; 72 + releaseMbId?: string; 73 + isrc?: string; 74 + originUrl: string; 75 + musicServiceBaseDomain: string; 76 + submissionClientAgent: string; 77 + playedTime: string; 78 + } 79 + 80 + interface ReleaseSelections { 81 + [key: string]: MusicBrainzRelease; 82 + } 83 + 84 + async function searchMusicbrainz( 85 + searchParams: SearchParams, 86 + ): Promise<MusicBrainzRecording[]> { 25 87 try { 26 - const queryParts = []; 88 + const queryParts: string[] = []; 27 89 if (searchParams.track) 28 90 queryParts.push(`release title:"${searchParams.track}"`); 29 91 if (searchParams.artist) ··· 44 106 } 45 107 } 46 108 47 - const SearchResult = ({ 109 + const SearchResult: React.FC<SearchResultProps> = ({ 48 110 result, 49 111 onSelectTrack, 50 112 isSelected, 51 113 selectedRelease, 52 114 onReleaseSelect, 53 115 }) => { 54 - const [showReleaseModal, setShowReleaseModal] = useState(false); 116 + const [showReleaseModal, setShowReleaseModal] = useState<boolean>(false); 55 117 56 118 const currentRelease = selectedRelease || result.releases?.[0]; 57 119 ··· 85 147 </Text> 86 148 87 149 {/* Release Selector Button */} 88 - {result.releases?.length > 0 && ( 150 + {result.releases && result.releases?.length > 0 && ( 89 151 <TouchableOpacity 90 152 onPress={() => setShowReleaseModal(true)} 91 153 className="p-1 bg-secondary/10 rounded-lg flex md:flex-row items-start md:gap-1" ··· 182 244 183 245 export default function TabTwoScreen() { 184 246 const agent = useStore((state) => state.pdsAgent); 185 - const [searchFields, setSearchFields] = useState({ 247 + const [searchFields, setSearchFields] = useState<SearchParams>({ 186 248 track: "", 187 249 artist: "", 188 250 release: "", 189 251 }); 190 - const [searchResults, setSearchResults] = useState([]); 191 - const [selectedTrack, setSelectedTrack] = useState(null); 192 - const [selectedRelease, setSelectedRelease] = useState(null); 193 - const [isLoading, setIsLoading] = useState(false); 194 - const [isSubmitting, setIsSubmitting] = useState(false); 195 - 196 - const [releaseSelections, setReleaseSelections] = useState({}); 252 + const [searchResults, setSearchResults] = useState<MusicBrainzRecording[]>( 253 + [], 254 + ); 255 + const [selectedTrack, setSelectedTrack] = 256 + useState<MusicBrainzRecording | null>(null); 257 + const [isLoading, setIsLoading] = useState<boolean>(false); 258 + const [isSubmitting, setIsSubmitting] = useState<boolean>(false); 259 + const [releaseSelections, setReleaseSelections] = useState<ReleaseSelections>( 260 + {}, 261 + ); 197 262 198 - const handleTrackSelect = (track) => { 263 + const handleTrackSelect = (track: MusicBrainzRecording | null): void => { 199 264 setSelectedTrack(track); 200 - // Reset selected release when track is deselected 201 - if (!track) { 202 - setSelectedRelease(null); 203 - } 204 265 }; 205 266 206 - const handleSearch = async () => { 267 + const handleSearch = async (): Promise<void> => { 207 268 if (!searchFields.track && !searchFields.artist && !searchFields.release) { 208 269 return; 209 270 } ··· 215 276 setIsLoading(false); 216 277 }; 217 278 218 - const createPlayRecord = (result) => { 279 + const createPlayRecord = (result: MusicBrainzRecording): PlayRecord => { 219 280 return { 220 281 trackName: result.title ?? "Unknown Title", 221 282 recordingMbId: result.id ?? undefined, ··· 235 296 }; 236 297 }; 237 298 238 - const submitPlay = async () => { 299 + const submitPlay = async (): Promise<void> => { 239 300 if (!selectedTrack) return; 240 301 241 302 setIsSubmitting(true);