Create-account links for Last.fm and ListenBrainz, and Link Spotify for ListenBrainz

ListenBrainz accounts are MusicBrainz accounts, so its sign-up link goes to MusicBrainz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 12:02:40 -04:00
co-authored by Claude Opus 5.5
parent d64e810866
commit 804493aee0
5 changed files with 41 additions and 15 deletions
@@ -1,8 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { Linking } from 'react-native';
import { mutate } from 'swr'; import { mutate } from 'swr';
import { SERVICE_LABELS } from '@musicshare/shared'; import { SERVICE_LABELS } from '@musicshare/shared';
import { SignInError, signInWithService } from '@/lib/connect'; import { SignInError, openSignupPage, signInWithService } from '@/lib/connect';
import { Button } from '../ui'; import { Button } from '../ui';
import { ErrorText, SetupActions, SetupHeader, type SetupProps } from './common'; import { ErrorText, SetupActions, SetupHeader, type SetupProps } from './common';
@@ -57,7 +56,7 @@ export function ServiceSignInSetup({
/> />
<ErrorText>{error}</ErrorText> <ErrorText>{error}</ErrorText>
<SetupActions primary="Sign in with Last.fm" loading={busy} onPrimary={run} onSkip={onDone} skipLabel={skipLabel} /> <SetupActions primary="Sign in with Last.fm" loading={busy} onPrimary={run} onSkip={onDone} skipLabel={skipLabel} />
<Button small title="New to Last.fm? Create an account" onPress={() => Linking.openURL('https://www.last.fm/join')} /> <Button small title="New to Last.fm? Create an account" onPress={() => openSignupPage('lastfm')} />
</> </>
); );
} }
@@ -1,9 +1,9 @@
import { useState } from 'react'; import { useState } from 'react';
import { Linking, Pressable, StyleSheet, Text, View } from 'react-native'; import { Pressable, StyleSheet, Text, View } from 'react-native';
import useSWR, { mutate } from 'swr'; import useSWR, { mutate } from 'swr';
import type { ConnectionDTO } from '@musicshare/shared'; import type { ConnectionDTO } from '@musicshare/shared';
import { api } from '@/lib/api'; import { api } from '@/lib/api';
import { SignInError, openSpotifyLinkPage, signInWithService } from '@/lib/connect'; import { SignInError, openSignupPage, openSpotifyLinkPage, signInWithService } from '@/lib/connect';
import { successHaptic } from '@/lib/haptics'; import { successHaptic } from '@/lib/haptics';
import { colors, radius, space } from '@/lib/theme'; import { colors, radius, space } from '@/lib/theme';
import { Body, Button, Field } from '../ui'; import { Body, Button, Field } from '../ui';
@@ -108,7 +108,7 @@ export function SpotifySetup({ onDone, skipLabel }: SetupProps) {
onPress={() => setVia('listenbrainz')} onPress={() => setVia('listenbrainz')}
title="ListenBrainz" title="ListenBrainz"
badge="Open source" badge="Open source"
detail="Free and run by a nonprofit. Enter your ListenBrainz username." detail="Free and run by a nonprofit. Enter your ListenBrainz username, then approve Spotify on ListenBrainz."
/> />
{via === 'listenbrainz' && ( {via === 'listenbrainz' && (
@@ -123,9 +123,16 @@ export function SpotifySetup({ onDone, skipLabel }: SetupProps) {
)} )}
<Button title={skipLabel ?? 'Not now'} onPress={onDone} /> <Button title={skipLabel ?? 'Not now'} onPress={onDone} />
</View> </View>
<Body muted style={styles.small} onPress={() => Linking.openURL(via === 'listenbrainz' ? 'https://listenbrainz.org/login/' : 'https://www.last.fm/join')}> {via && (
No account yet? Create one free on {via === 'listenbrainz' ? 'ListenBrainz' : 'Last.fm'} first. <View style={{ gap: space.xs }}>
</Body> <Button small title={`No account? Create one on ${via === 'listenbrainz' ? 'MusicBrainz' : 'Last.fm'}`} onPress={() => openSignupPage(via)} />
{via === 'listenbrainz' && (
<Body muted style={styles.small}>
ListenBrainz uses MusicBrainz accounts. After creating one, sign in to ListenBrainz once, then come back.
</Body>
)}
</View>
)}
</> </>
); );
} }
+4 -1
View File
@@ -1,6 +1,6 @@
import * as Linking from 'expo-linking'; import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser'; import * as WebBrowser from 'expo-web-browser';
import { SPOTIFY_LINK_PAGES } from '@musicshare/shared'; import { SIGNUP_PAGES, SPOTIFY_LINK_PAGES } from '@musicshare/shared';
import { api } from './api'; import { api } from './api';
export type SignInResult = 'connected' | 'cancelled'; export type SignInResult = 'connected' | 'cancelled';
@@ -25,3 +25,6 @@ export async function signInWithService(provider: 'plex' | 'lastfm'): Promise<Si
/** Where users approve "Spotify Scrobbling" (Last.fm) or the Spotify link (ListenBrainz). */ /** Where users approve "Spotify Scrobbling" (Last.fm) or the Spotify link (ListenBrainz). */
export const openSpotifyLinkPage = (on: keyof typeof SPOTIFY_LINK_PAGES = 'lastfm') => export const openSpotifyLinkPage = (on: keyof typeof SPOTIFY_LINK_PAGES = 'lastfm') =>
WebBrowser.openBrowserAsync(SPOTIFY_LINK_PAGES[on]); WebBrowser.openBrowserAsync(SPOTIFY_LINK_PAGES[on]);
/** Their sign-up page, in the in-app browser (ListenBrainz accounts start on MusicBrainz). */
export const openSignupPage = (service: keyof typeof SIGNUP_PAGES) => WebBrowser.openBrowserAsync(SIGNUP_PAGES[service]);
+13 -5
View File
@@ -6,6 +6,7 @@ import { Suspense, useEffect, useRef, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { import {
SERVICE_LABELS, SERVICE_LABELS,
SIGNUP_PAGES,
SPOTIFY_LINK_PAGES, SPOTIFY_LINK_PAGES,
STREAMING_SERVICES, STREAMING_SERVICES,
isQuietScrobbler, isQuietScrobbler,
@@ -317,14 +318,21 @@ function Connections() {
</span> </span>
)} )}
</div> </div>
{!byProvider.has(p) && (
<div className="muted small">
No account?{' '}
<a href={SIGNUP_PAGES[p]} target="_blank" rel="noreferrer">
Create one free on {p === 'lastfm' ? 'Last.fm' : 'MusicBrainz'}
</a>
{p === 'listenbrainz' ? ', then sign in to ListenBrainz once (it uses MusicBrainz accounts).' : '.'}
</div>
)}
</div> </div>
{byProvider.has(p) ? ( {byProvider.has(p) ? (
<div className="row"> <div className="row">
{p === 'lastfm' && ( <a className="btn" href={SPOTIFY_LINK_PAGES[p]} target="_blank" rel="noreferrer">
<a className="btn" href="https://www.last.fm/settings/applications" target="_blank" rel="noreferrer"> Link Spotify
Link Spotify </a>
</a>
)}
<button className="btn" onClick={() => disconnect(p)}> <button className="btn" onClick={() => disconnect(p)}>
Disconnect Disconnect
</button> </button>
+9
View File
@@ -103,6 +103,15 @@ export const SPOTIFY_LINK_PAGES = {
listenbrainz: 'https://listenbrainz.org/settings/music-services/details/', listenbrainz: 'https://listenbrainz.org/settings/music-services/details/',
} as const; } as const;
/**
* Where to make a free account (neither lets other apps create one). ListenBrainz accounts are
* MusicBrainz accounts: register there, then sign in to ListenBrainz once to start it.
*/
export const SIGNUP_PAGES = {
lastfm: 'https://www.last.fm/join',
listenbrainz: 'https://musicbrainz.org/register',
} as const;
const QUIET_AFTER_MS = 7 * 86_400_000; const QUIET_AFTER_MS = 7 * 86_400_000;
/** /**