Skip to content

Commit a89a2bb

Browse files
committed
wip
1 parent 8ef6fac commit a89a2bb

File tree

8 files changed

+1099
-45
lines changed

8 files changed

+1099
-45
lines changed

deno.lock

Lines changed: 832 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/web/src/data/tracks-api.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,62 @@ export type ListenedTracks = Omit<Listened, "hooman_id"> & {
1010

1111
export async function getLastListenedTracks(
1212
signal: AbortSignal,
13+
page: number = 0,
1314
): Promise<ListenedTracks[]> {
14-
const { data, error } = await supabase
15-
.from("listened")
16-
.select<string, ListenedTracks>(`
17-
id,
18-
artist_name,
19-
track_name,
20-
album_lastfm_id,
21-
album_name,
22-
created_at,
23-
listened_at,
24-
lastfm_id,
25-
hooman:hooman_id (
26-
id,
27-
lastfm_user
28-
)
29-
`)
30-
.order("listened_at", { ascending: false })
31-
.limit(50)
32-
.abortSignal(signal);
33-
34-
if (error) throw error;
35-
return data ?? [];
15+
const limit = 50;
16+
const offset = page * limit;
17+
18+
console.log('Fetching tracks with page:', page, 'offset:', offset);
19+
20+
try {
21+
// First, check if we can connect to Supabase at all
22+
const healthCheck = await supabase.from('listened').select('count()', { count: 'exact' });
23+
console.log('Supabase health check:', healthCheck);
24+
25+
// Now perform the actual query
26+
const { data, error } = await supabase
27+
.from("listened")
28+
.select<string, ListenedTracks>(`
29+
id,
30+
artist_name,
31+
track_name,
32+
album_lastfm_id,
33+
album_name,
34+
created_at,
35+
listened_at,
36+
lastfm_id,
37+
hooman:hooman_id (
38+
id,
39+
lastfm_user
40+
)
41+
`)
42+
.order("listened_at", { ascending: false })
43+
.range(offset, offset + limit - 1)
44+
.abortSignal(signal);
45+
46+
console.log('Supabase response:', {
47+
dataReceived: !!data,
48+
dataLength: data?.length || 0,
49+
error: error ? error.message : null,
50+
firstItem: data && data.length > 0 ? {
51+
id: data[0].id,
52+
artist: data[0].artist_name,
53+
track: data[0].track_name
54+
} : null
55+
});
56+
57+
if (error) {
58+
console.error('Supabase error details:', error);
59+
throw error;
60+
}
61+
62+
if (!data || data.length === 0) {
63+
console.warn('No data returned from Supabase query');
64+
}
65+
66+
return data ?? [];
67+
} catch (error) {
68+
console.error('Error fetching tracks:', error);
69+
throw error;
70+
}
3671
}

src/web/src/i18n/en.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"headline": "Music",
3+
"lastListened": "Last listened",
4+
"visitUserProfile": "Visit user profile",
5+
"time": {
6+
"seconds": "seconds",
7+
"minutes": "minutes",
8+
"hours": "hours",
9+
"days": "days"
10+
},
11+
"noTracksFound": "No tracks found. Please check your connection to Supabase.",
12+
"retry": "Retry",
13+
"loading": "Loading...",
14+
"loadMore": "Load more",
15+
"github": "Show code of this project on GitHub"
16+
}

src/web/src/lib/supabase.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,14 @@ import type { Database } from "../../../shared/db.types.ts";
44
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
55
const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
66

7+
console.log('Supabase configuration:', {
8+
supabaseUrl: supabaseUrl ? 'URL is set' : 'URL is missing',
9+
supabaseKey: supabaseKey ? 'Key is set' : 'Key is missing'
10+
});
11+
12+
if (!supabaseUrl || !supabaseKey) {
13+
console.error('Supabase URL or key is missing. Check your environment variables.');
14+
}
15+
716
export const supabase = createClient<Database>(supabaseUrl, supabaseKey);
817
export type { Database };

src/web/src/ui/feed/item/feed-item.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ export function FeedItem(props: Props) {
2121
const { t, i18n } = useLingui();
2222
const { artist, album, track, user, listenedAt } = props;
2323

24+
console.log('FeedItem received props:', { artist, album, track, user, listenedAt });
25+
2426
function handleOnClick(e: React.MouseEvent<HTMLDivElement>) {
2527
e.preventDefault();
2628
toast.promise(navigator.clipboard.writeText(`${artist} - ${track}`), {
Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,64 @@
1-
import { FeedItem } from "../item/feed-item.tsx";
2-
import { ListenedTracks } from "../../../data/tracks-api.ts";
1+
import { FeedItem } from "./feed-item.tsx";
2+
import { ListenedTracks } from "../../data/tracks-api.ts";
33
import "./feed.module.css";
4-
import { MusicLoader } from "../../activity/loader.tsx";
4+
import { MusicLoader } from "../activity/loader.tsx";
5+
import { i18n } from "../../i18n/i18n.ts";
56

67
interface FeedListProps {
78
data: ListenedTracks[];
89
error: Error | null;
910
isLoading: boolean;
1011
isFetching: boolean;
12+
onLoadMore?: () => void;
13+
hasMoreData?: boolean;
1114
}
1215

1316
export function FeedList(props: FeedListProps) {
14-
const { data, isLoading, isFetching } = props;
17+
const { data, isLoading, error, isFetching, onLoadMore, hasMoreData = false } = props;
18+
19+
console.log('FeedList received data:', data);
20+
console.log('FeedList props:', { isLoading, isFetching, hasMoreData });
1521

1622
if (isLoading) {
1723
return null;
1824
}
1925

26+
// Check if we have data to display
27+
const hasData = data && data.length > 0;
28+
2029
return (
2130
<div className="feed" data-fetching={isFetching}>
2231
{isFetching && <MusicLoader size={32} className="feed__fetching" />}
23-
{data.map((item: ListenedTracks) => (
24-
<FeedItem
25-
key={item.id}
26-
artist={item.artist_name}
27-
album={item.album_name}
28-
track={item.track_name}
29-
listenedAt={item.listened_at}
30-
user={item.hooman?.lastfm_user}
31-
/>
32-
))}
32+
33+
{hasData ? (
34+
// Render items if we have data
35+
data.map((item: ListenedTracks) => (
36+
<FeedItem
37+
key={item.id}
38+
artist={item.artist_name}
39+
album={item.album_name}
40+
track={item.track_name}
41+
listenedAt={item.listened_at}
42+
user={item.hooman?.lastfm_user}
43+
/>
44+
))
45+
) : (
46+
// This will be shown if data is empty but not loading and no error
47+
// The main error/empty state handling is in IndexView
48+
!isLoading && !error && <div className="feed__empty"></div>
49+
)}
50+
51+
{!error && hasData && hasMoreData && onLoadMore && (
52+
<div className="feed__load-more">
53+
<button
54+
onClick={onLoadMore}
55+
disabled={isFetching}
56+
className="feed__load-more-button"
57+
>
58+
{isFetching ? i18n.loading : i18n.loadMore}
59+
</button>
60+
</div>
61+
)}
3362
</div>
3463
);
3564
}

src/web/src/ui/feed/list/feed.module.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@
99
top: -150px;
1010
right: 0;
1111
}
12+
13+
.feed__load-more {
14+
display: flex;
15+
justify-content: center;
16+
margin: 20px 0;
17+
}
18+
19+
.feed__load-more-button {
20+
padding: 10px 20px;
21+
border-radius: 6px;
22+
background: color-mix(in oklab, var(--color-neutral-900)55%, transparent);
23+
color: #fff;
24+
border: 1px solid color-mix(in oklab, var(--color-neutral-700)75%, transparent);
25+
cursor: pointer;
26+
font-size: 14px;
27+
transition: 0.3s all cubic-bezier(0.4, 0, 0.2, 1);
28+
29+
&:hover {
30+
background: #1e1e1e;
31+
border-color: #2f2f2f;
32+
}
33+
34+
&:disabled {
35+
opacity: 0.6;
36+
cursor: not-allowed;
37+
}
38+
}
1239
}
1340

1441
.feed_row {

src/web/src/view/index-view.tsx

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,133 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { useQuery } from "../lib/react-query.tsx";
45
import { getLastListenedTracks, ListenedTracks } from "../data/tracks-api.ts";
5-
import { FeedList } from "../ui/feed/list/feed-list.tsx";
6+
import { FeedList } from "../ui/feed/feed-list.tsx";
67
import { MusicLoader } from "../ui/activity/loader.tsx";
7-
import { Header } from "../ui/header/header.tsx";
8+
import { Header } from "../ui/header.tsx";
9+
import {i18n} from "../i18n/i18n.ts";
810

911
export function IndexView() {
10-
const { isLoading, isFetching, error, data = [] } = useQuery<
12+
const [page, setPage] = useState(0);
13+
const [allTracks, setAllTracks] = useState<ListenedTracks[]>([]);
14+
15+
console.log('IndexView rendered with state:', { page, allTracksLength: allTracks.length });
16+
17+
const { isLoading, isFetching, error, data = [], refetch } = useQuery<
1118
ListenedTracks[]
1219
>({
13-
queryKey: ["lastListenedTracks"],
14-
queryFn: ({ signal }) => getLastListenedTracks(signal),
20+
queryKey: ["lastListenedTracks", page],
21+
queryFn: ({ signal }) => getLastListenedTracks(signal, page),
22+
onSuccess: (newData) => {
23+
console.log('Query success, received data:', newData);
24+
if (page === 0) {
25+
console.log('Setting initial tracks');
26+
setAllTracks(newData);
27+
} else {
28+
console.log('Appending new tracks to existing ones');
29+
setAllTracks((prev) => {
30+
const updatedTracks = [...prev, ...newData];
31+
console.log('Updated tracks array:', updatedTracks);
32+
return updatedTracks;
33+
});
34+
}
35+
},
36+
onError: (err) => {
37+
console.error('Query error:', err);
38+
},
39+
retry: 3, // Retry failed requests 3 times
40+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff
41+
staleTime: 5 * 60 * 1000, // 5 minutes
42+
refetchOnWindowFocus: false, // Don't refetch when window regains focus
43+
});
44+
45+
const loadMore = () => {
46+
setPage((prevPage) => prevPage + 1);
47+
};
48+
49+
const hasMoreData = data.length === 50;
50+
51+
console.log('Before rendering FeedList:', {
52+
allTracksLength: allTracks.length,
53+
dataLength: data.length,
54+
isLoading,
55+
isFetching,
56+
error: error ? (error as Error).message : null
1557
});
1658

59+
// Function to retry fetching data
60+
const retryFetch = async () => {
61+
console.log('Retrying data fetch...');
62+
// Reset page to 0 and clear tracks
63+
setPage(0);
64+
setAllTracks([]);
65+
// Use React Query's refetch function
66+
try {
67+
await refetch();
68+
console.log('Refetch initiated');
69+
} catch (err) {
70+
console.error('Refetch failed:', err);
71+
}
72+
};
73+
1774
return (
1875
<div className="index-view">
1976
<Header />
20-
{isLoading && <MusicLoader size={64} center paddingTop={40} />}
21-
{error && <div>Error: {(error as Error).message}</div>}
77+
{isLoading && page === 0 && <MusicLoader size={64} center paddingTop={40} />}
78+
79+
{error && (
80+
<div style={{ textAlign: 'center', marginTop: '20px', color: 'red' }}>
81+
<p>Error: {(error as Error).message}</p>
82+
<button
83+
onClick={retryFetch}
84+
style={{
85+
padding: '10px 20px',
86+
marginTop: '10px',
87+
borderRadius: '6px',
88+
background: '#1e1e1e',
89+
color: '#fff',
90+
border: '1px solid #2f2f2f',
91+
cursor: 'pointer'
92+
}}
93+
>
94+
Retry
95+
</button>
96+
</div>
97+
)}
98+
99+
{allTracks.length === 0 && !isLoading && !error && (
100+
<div style={{ textAlign: 'center', marginTop: '20px' }}>
101+
<p>No tracks found. This could be due to:</p>
102+
<ul style={{ listStyle: 'none', padding: 0 }}>
103+
<li>- No data in the database</li>
104+
<li>- Connection issues with Supabase</li>
105+
<li>- Authentication problems</li>
106+
</ul>
107+
<button
108+
onClick={retryFetch}
109+
style={{
110+
padding: '10px 20px',
111+
marginTop: '10px',
112+
borderRadius: '6px',
113+
background: '#1e1e1e',
114+
color: '#fff',
115+
border: '1px solid #2f2f2f',
116+
cursor: 'pointer'
117+
}}
118+
>
119+
{i18n.retry}
120+
</button>
121+
</div>
122+
)}
123+
22124
<FeedList
23-
isLoading={isLoading}
125+
isLoading={isLoading && page === 0}
24126
isFetching={isFetching}
25127
error={error}
26-
data={data}
128+
data={allTracks}
129+
onLoadMore={loadMore}
130+
hasMoreData={hasMoreData}
27131
/>
28132
</div>
29133
);

0 commit comments

Comments
 (0)