Add pause controls and app attribution to the web app
Settings gets a Sharing card (pause 1 hour / until tomorrow / until turned back on); the feed shows a resume banner while paused. Now-playing cards say which app a phone reported, e.g. "via Spotify". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -4,6 +4,7 @@ import Link from 'next/link';
|
||||
import useSWR from 'swr';
|
||||
import { sortFeed, type FeedItemDTO, type ProfileDTO } from '@musicshare/shared';
|
||||
import { NowPlayingCard } from '@/components/NowPlayingCard';
|
||||
import { SharingControls } from '@/components/SharingControls';
|
||||
import { UserSearch } from '@/components/UserSearch';
|
||||
import { api, fetcher, useMe } from '@/lib/api';
|
||||
import { useRealtime } from '@/lib/useRealtime';
|
||||
@@ -53,6 +54,7 @@ function Feed() {
|
||||
return (
|
||||
<div className="stack">
|
||||
<UserSearch />
|
||||
<SharingControls compact />
|
||||
{mine?.nowPlaying && (
|
||||
<section className="stack">
|
||||
<h2 className="section-label">You</h2>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type NowPlayingProvider,
|
||||
type TrackSearchResultDTO,
|
||||
} from '@musicshare/shared';
|
||||
import { SharingControls } from '@/components/SharingControls';
|
||||
import { api, fetcher, useMe } from '@/lib/api';
|
||||
import { timeAgo } from '@musicshare/shared';
|
||||
import { useDebounced } from '@/lib/useDebounced';
|
||||
@@ -32,6 +33,7 @@ function Settings() {
|
||||
<div className="stack">
|
||||
<h1>Settings</h1>
|
||||
<ProfileForm me={me} />
|
||||
<SharingControls />
|
||||
<Connections />
|
||||
<FeaturedPicker />
|
||||
<DangerZone />
|
||||
|
||||
@@ -31,7 +31,7 @@ export function NowPlayingCard({
|
||||
<span className="row">
|
||||
<span className={`status ${np.isPlaying ? 'status-live' : ''}`}>
|
||||
{np.isPlaying ? 'Listening now' : `Played ${timeAgo(np.updatedAt)}`}
|
||||
<span className="muted"> via {SERVICE_LABELS[np.source]}</span>
|
||||
<span className="muted"> via {np.sourceApp ?? SERVICE_LABELS[np.source]}</span>
|
||||
</span>
|
||||
{onTogglePin && (
|
||||
<button
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { pausedUntilLabel, type MeDTO } from '@musicshare/shared';
|
||||
import { api, useMe } from '@/lib/api';
|
||||
|
||||
/** Pause/resume sharing. Compact mode is the one-line banner on the feed. */
|
||||
export function SharingControls({ compact }: { compact?: boolean }) {
|
||||
const { me, mutate } = useMe();
|
||||
const [busy, setBusy] = useState(false);
|
||||
if (!me) return null;
|
||||
|
||||
const set = async (pause: '1h' | '8h' | 'indefinitely' | null) => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await mutate(await api<MeDTO>('/me/sharing', { method: 'PUT', body: { pause } }), { revalidate: false });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (compact) {
|
||||
if (!me.sharingPausedUntil) return null;
|
||||
return (
|
||||
<p className="notice row">
|
||||
<span className="grow">Sharing is paused {pausedUntilLabel(me.sharingPausedUntil)}.</span>
|
||||
<button className="btn btn-small btn-primary" onClick={() => set(null)} disabled={busy}>
|
||||
Resume
|
||||
</button>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="card form">
|
||||
<h2>Sharing</h2>
|
||||
{me.sharingPausedUntil ? (
|
||||
<>
|
||||
<p className="muted">Paused {pausedUntilLabel(me.sharingPausedUntil)}. Friends don’t see what you play, and nothing is added to your history.</p>
|
||||
<button className="btn btn-primary" onClick={() => set(null)} disabled={busy} style={{ alignSelf: 'flex-start' }}>
|
||||
Resume sharing
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="muted">Friends see what you’re playing. Pause any time.</p>
|
||||
<div className="row">
|
||||
<button className="btn" onClick={() => set('1h')} disabled={busy}>
|
||||
Pause for 1 hour
|
||||
</button>
|
||||
<button className="btn" onClick={() => set('8h')} disabled={busy}>
|
||||
Until tomorrow
|
||||
</button>
|
||||
<button className="btn" onClick={() => set('indefinitely')} disabled={busy}>
|
||||
Until I turn it back on
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user