Skip to content

Commit

Permalink
Simplify login and make tracks selectable
Browse files Browse the repository at this point in the history
  • Loading branch information
fyrk committed Sep 7, 2023
1 parent cfedada commit d40fc73
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 113 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && mkdir -p ./dist/app && cp ./dist/index.html ./dist/app/index.html",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
Expand Down
63 changes: 63 additions & 0 deletions src/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { IRedirectionStrategy, SpotifyApi } from "@spotify/web-api-ts-sdk"
import { useEffect, useState } from "preact/hooks"
import { createApiClient } from "./api"
import App from "./app/app"
import Home from "./home"

export default function Main() {
const [spotify, setSpotify] = useState<SpotifyApi>(null)
const [authError, setAuthError] = useState<string>(null)

useEffect(() => {
// check if user is already authenticated
;(async () => {
try {
// do not redirect here, since this means user is not authenticated, do nothing instead
class NoRedirectionStrategy implements IRedirectionStrategy {
public async redirect(_targetUrl: string | URL): Promise<void> {
return
}
public async onReturnFromRedirect(): Promise<void> {}
}
const sdk = createApiClient(new NoRedirectionStrategy())
const { authenticated } = await sdk.authenticate()
if (authenticated && !spotify) {
setSpotify(sdk)
}
} catch (e) {
console.info("User is not authenticated", e)
}
})()
}, [])

if (spotify) {
return (
<App
onLogout={() => {
spotify.logOut()
setSpotify(null)
}}
spotify={spotify}
/>
)
}

return (
<Home
authError={authError}
onLogin={async () => {
try {
const sdk = createApiClient()
const { authenticated } = await sdk.authenticate()
if (authenticated) {
// this should never be reached since authenticate redirects
setSpotify(sdk)
}
} catch (e: Error | unknown) {
console.error(e)
setAuthError(e.toString() || "")
}
}}
/>
)
}
3 changes: 1 addition & 2 deletions src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const createApiClient = (
import.meta.env.VITE_SPOTIFY_CLIENT_ID,
import.meta.env.PROD
? import.meta.env.VITE_SPOTIFY_REDIRECT_URI
: "http://localhost:3000/app",
: "http://localhost:3000",
Scopes.playlistRead,
config,
)
Expand All @@ -35,7 +35,6 @@ async function* getPaginatedItems<T>(
): AsyncGenerator<{ total: number; item: T }> {
let resp = await firstResponse
const items = resp.items
let counter = 1
for (let item of items) {
yield Promise.resolve({ total: resp.total, item })
}
Expand Down
159 changes: 93 additions & 66 deletions src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { SpotifyApi } from "@spotify/web-api-ts-sdk"
import { route } from "preact-router"
import { useEffect, useState } from "preact/hooks"
import {
IRedirectionStrategy,
User,
} from "../../node_modules/@spotify/web-api-ts-sdk/src/types"
import { createApiClient } from "../api"
import { User } from "../../node_modules/@spotify/web-api-ts-sdk/src/types"
import Scaffold from "../scaffold"
import { PlaylistsView } from "./components"
import {
Expand All @@ -15,43 +10,26 @@ import {
} from "./util"
import noScooterCircle from "/img/no_scooter_circle.svg?url"

export default function App(_props: any) {
export default function App({
onLogout,
spotify,
}: {
onLogout: () => void
spotify: SpotifyApi
}) {
const [user, setUser] = useState<User>(null)
const [scanProgress, setScanProgress] = useState<PlaylistScanProgress>({
progress: 0,
currentPlaylistName: null,
})
const [replaces, setReplaces] = useState<{
spotify: SpotifyApi
playlists: ScannedPlaylist[]
}>(null)
const [playlists, setPlaylists] = useState<ScannedPlaylist[]>(null)

useEffect(() => {
;(async () => {
// do not redirect here, since this means user is not authenticated
// route to Home instead
class HomeRedirectionStrategy implements IRedirectionStrategy {
public async redirect(_targetUrl: string | URL): Promise<void> {
route("/")
}
public async onReturnFromRedirect(): Promise<void> {}
}
const spotify = createApiClient(new HomeRedirectionStrategy())
const accessToken = await spotify.authenticate()
if (accessToken.access_token === "") {
route("/")
} else {
const user = await spotify.currentUser.profile()
setUser(user)

const playlists = await scanUserPlaylists(
spotify,
user,
setScanProgress,
)

setReplaces({ playlists, spotify })
}
const user = await spotify.currentUser.profile()
setUser(user)
const playlists = await scanUserPlaylists(spotify, user, setScanProgress)
setPlaylists(playlists)
})()
}, [])

Expand All @@ -68,55 +46,35 @@ export default function App(_props: any) {
<div class="my-2 inline-block">Taylor’s Version</div>
</div>
<div class="float-right my-2 inline-block align-middle">
<LogOutButton
user={user}
onLogOut={() => {
localStorage.removeItem(
"spotify-sdk:AuthorizationCodeWithPKCEStrategy:token",
)
route("/")
}}
/>
<LogOutButton user={user} onLogout={onLogout} />
</div>
</header>
{replaces == null ? (
<div class="text-center">
<div class="mb-2 text-lg">Scanning your playlists...</div>
<div class="h-2 w-44 rounded-full bg-neutral-600">
<div
class="h-full rounded-full bg-[#1db954]"
style={{ width: `${scanProgress.progress * 100}%` }}
></div>
</div>
<div class="text-neutral-400">
{scanProgress.currentPlaylistName || <>&nbsp;</>}
</div>
</div>
{playlists == null ? (
<ScanProgress scanProgress={scanProgress} />
) : (
<div class="w-full grow p-5">
<PlaylistsView
playlists={replaces.playlists}
spotify={replaces.spotify}
/>
</div>
<PlaylistEditor
playlists={playlists}
onUpdatePlaylists={p => setPlaylists(p)}
spotify={spotify}
/>
)}
</Scaffold>
)
}

function LogOutButton({
user,
onLogOut,
onLogout,
}: {
user: User
onLogOut: () => void
onLogout: () => void
}) {
const profileImage =
user && user.images.reduce((p, c) => (p.width < c.width ? c : p))
return (
<button
class="rounded-full bg-[#555555] p-2 text-sm font-semibold sm:text-xl"
onClick={onLogOut}
onClick={onLogout}
>
<span class="flex items-center gap-2">
<img
Expand All @@ -128,3 +86,72 @@ function LogOutButton({
</button>
)
}

const ScanProgress = ({
scanProgress,
}: {
scanProgress: PlaylistScanProgress
}) => {
return (
<div class="text-center">
<div class="mb-2 text-lg">Scanning your playlists...</div>
<div class="h-2 w-44 rounded-full bg-neutral-600">
<div
class="h-full rounded-full bg-[#1db954]"
style={{ width: `${scanProgress.progress * 100}%` }}
></div>
</div>
<div class="text-neutral-400">
{scanProgress.currentPlaylistName || <>&nbsp;</>}
</div>
</div>
)
}

const PlaylistEditor = ({
playlists,
onUpdatePlaylists,
spotify,
}: {
playlists: ScannedPlaylist[]
onUpdatePlaylists: (playlists: ScannedPlaylist[]) => void
spotify: SpotifyApi
}) => {
return (
<div class="w-full grow p-5">
<PlaylistsView
playlists={playlists}
spotify={spotify}
onSelectPlaylist={(playlistIndex: number, selected: boolean) => {
onUpdatePlaylists(
playlists.map((p, i) => ({
...p,
replacements: p.replacements.map(r => ({
...r,
selected: i === playlistIndex ? selected : r.selected,
})),
})),
)
}}
onSelectTrack={(
playlistIndex: number,
trackIndex: number,
selected: boolean,
) => {
onUpdatePlaylists(
playlists.map((p, i) => ({
...p,
replacements: p.replacements.map(r => ({
...r,
selected:
i === playlistIndex && r.position === trackIndex
? selected
: r.selected,
})),
})),
)
}}
/>
</div>
)
}
Loading

0 comments on commit d40fc73

Please sign in to comment.