+84
-23
apps/amethyst/app/(tabs)/stamp.tsx
+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);