I am trying to make use of spotify api to play songs in my webapp. I have implemented the authorization using next-auth with spotify and with the session I have pulled all my playlists to show in a list. However, when I try to play a song in that list even though I have given all the correct scopes for the PUT request and i am using a premium spotify account, it gives me this error of 401 "permissions missing".
Here is my code where I have authenticated and also given all the needed scopes.
const scopes = [
"user-read-email",
"playlist-read-private",
"playlist-read-collaborative",
"streaming",
"user-read-private",
"user-library-read",
"user-top-read",
"user-read-playback-state",
"user-modify-playback-state",
"user-read-currently-playing",
"user-read-recently-played",
"user-follow-read",
"user-library-modify",
"user-follow-modify",
"playlist-modify-public",
].join(",");
const params = {
scope: scopes,
};
const queryParamString =new URLSearchParams(params);
const LOGIN_URL = "https://accounts.spotify.com/authorize?"+ queryParamString.toString();
const spotifyApi = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
})
export default spotifyApi;
export { LOGIN_URL };
I tried everything including reading though the spotify api docs but nothing seems to work. Here is my code which I implemented for plaing a song:
import React from 'react';
import useSpotify from '../hooks/useSpotify';
import Image from 'next/image';
import { millisToMinutes } from '../lib/time';
import { useRecoilState } from 'recoil';
import { currentTrackIdState, isPlayingState } from '../atoms/songAtom';
function Song({order, track}) {
const spotifyApi = useSpotify();
const [currentTrackId, setCurrentTrackId] = useRecoilState(currentTrackIdState);
const [isPlaying, setIsPlaying] = useRecoilState(isPlayingState);
const playSong = () => {
setCurrentTrackId(track.track.id);
setIsPlaying(true);
spotifyApi.play({
uris: [track.track.uri],
});
};
console.log(track.track.uri);
return (
<div className='grid grid-cols-2 text-gray-500 py-4 px-5 hover:bg-gray-900 rounded-md cursor-pointer'
onClick={playSong}>
<div className='flex items-center space-x-4'>
<p>{order+1}</p>
<Image alt='' src={track.track.album.images[0].url} height={40} width={40} />
<div>
<p className='w-36 lg:w-64 truncate text-white'>{track.track.name}</p>
<p className='w-40'>{track.track.artists[0].name}</p>
</div>
</div>
<div className='flex items-center justify-between ml-auto md:ml-0'>
<p className='hidden md:inline w-40'>{track.track.album.name}</p>
<p>{millisToMinutes(track.track.duration_ms)}</p>
</div>
</div>
)
}
export default Song
Related
I am trying to populate a table of people with their name and a profile picture. The name is sourced from a Firestore database, and the picture uses the name to find a related picture in a Firebase Storage bucket.
I have watched hours of videos and have scoured nearly a hundred articles at this point, so my example code has pieces from each as I've been trying every combination and getting mixed but unsuccessful results.
In this current state which returns the least amount of errors, I am able to successfully populate the table with the names, however in that same component it is not able to pull the profile picture. The value used for the profile picture is updated, but it is updated from the placeholder value to undefined.
GamePlanner.vue
<template>
<div>
<Field />
<Bench />
<!-- <Suspense>
<template #default> -->
<PlanSummary />
<!-- </template>
<template #fallback>
<div class="loading">Loading...</div>
</template>
</Suspense> -->
</div>
</template>
PlanSummary.vue
<template>
<div class="summaryContainer">
<div class="inningTableContainer">
<table class="inningTable">
<tbody>
<!-- <Suspense> -->
<!-- <template #default> -->
<PlayerRow v-for="player in players.value" :key="player.id" :player="(player as Player)" />
<!-- </template> -->
<!-- <template #fallback>
<tr data-playerid="0">
<img class="playerImage" src="https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50" />
Loading players...
<span class="playerNumber">00</span>
</tr>
</template> -->
<!-- </Suspense> -->
</tbody>
</table>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onErrorCaptured, ref } from "vue";
import { useFirestore, useCollection } from "vuefire";
import { collection } from "firebase/firestore";
import { Player, Inning } from "#/definitions/GamePlanner";
import PlayerRow from "./PlayerRow.vue";
const db = useFirestore();
const gamePlanID = "O278vlB9Xx39vkZvIsdP";
// const players = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
// const players = ref(useCollection(collection(db, `/gameplans/${gamePlanID}/participants`)));
// Unhandled error during execution of scheduler flush
// Uncaught (in promise) DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
const players = ref();
players.value = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
// Seeminly infinite loop with "onServerPrefetch is called when there is no active component instance to be associated with."
// Renders 5 (??) undefined players
// One error shown: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'substring')
// const players = computed(() => useCollection(collection(db, `/gameplans/${gamePlanID}/participants`)));
// onErrorCaptured((error, vm, info) => {
// console.log("Error loading Summary component: ", error, "vm: ", vm, "info: ", info);
// throw error;
// });
</script>
PlayerRow.vue
<template>
<tr :key="player2.id" :data-playerid="player2.id">
<td>
<img class="playerImage" :src="playerPictureURL" />
{{ player2.nickname || player2.firstName + " " + player2.lastName }}
<span class="playerNumber">{{ player2.playerNumber }}</span>
</td>
</tr>
</template>
<script lang="ts" setup>
import { ref, PropType, computed, onMounted, watch } from "vue";
import { useFirebaseStorage, useStorageFileUrl } from "vuefire";
import { ref as storageRef } from 'firebase/storage';
import { Player, Inning } from "#/definitions/GamePlanner";
const fs = useFirebaseStorage();
const props = defineProps({
'player': { type: Object as PropType<Player>, required: true },
// 'innings': Array<Inning>
});
const player2 = ref(props.player);
// const innings = computed(() => props.innings);
// const playerPictureURL = computed(() => {
// const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
// const playerPictureResource = storageRef(fs, `playerPictures/${playerPictureFilename}`);
// return useStorageFileUrl(playerPictureResource).url.value as string;
// });
// const playerPictureURL = ref(() => {
// const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
// const playerPictureResource = storageRef(fs, `playerPictures/${playerPictureFilename}`);
// return useStorageFileUrl(playerPictureResource).url.value as string;
// });
const playerPictureURL = ref("https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50");
async function getPlayerPictureURL() {
console.log("PlayerRow.ts getPlayerPictureURL");
const playerPictureFilename = `${player2.value.firstName.substring(0,1)}${player2.value.lastName}.png`.toLowerCase();
const playerPictureResource = await storageRef(fs, `playerPictures/${playerPictureFilename}`);
playerPictureURL.value = await useStorageFileUrl(playerPictureResource).url.value as string;
}
onMounted(() => {
console.log("PlayerRow.ts onMounted");
getPlayerPictureURL();
});
watch(playerPictureURL, (newVal, oldVal) => {
console.log("PlayerRow.ts watch playerPictureURL");
console.log("newVal: " + newVal);
console.log("oldVal: " + oldVal);
});
</script>
I was under the impression that <Suspense> would need to wrap the <PlayerRow> component since I am using the storageRef and useStorageUrl methods, but it seems to introduce more issues. Based on the vuefire documentation and inspecting the definitions int he code itself, it does not appear that they are asynchronous, however trying to to immediately invoke them does not produce an immediate/actual result.
Relevant Package Versions
{
"vue": "^3.2.45"
"firebase": "^9.15.0",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vue-router": "^4.1.6",
"vue-tsc": "^1.0.11",
"vuefire": "3.0.0-beta.6"
}
According to this documentation we are supposed to use useCollection with including collection as a argument as follows:
const todos = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`))
And I see you are using it the correct way but instead of assigning to a ref you can assign it directly to players variable. As you are trying to use this reactive object as the value of a ref object, which is not supported.
You can solve your issue with changing this line:
const players = ref();
players.value = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
To :
const players = useCollection(collection(db, `/gameplans/${gamePlanID}/participants`));
For more information about this topic you can go through the following docs
So am practicing building this app, and I have integrated a Google sign-in using the Google Developers console. The problem is that I am getting this error on my console:
{
"error": "idpiframe_initialization_failed",
"details": "You have created a new client application that uses libraries for user authentication or authorization that will soon be deprecated. New clients must use the new libraries instead; existing clients must also migrate before these libraries are deprecated. See the Migration Guide for more information."
}
When I click the sign-in button another error then prints on my console as a second error:
{
"error": "popup_closed_by_user"
}
This is how my Login.jsx file looks like
import React from 'react';
import { GoogleLogin } from 'react-google-login';
import { useNavigate } from 'react-router-dom';
import { FcGoogle } from 'react-icons/fc';
import shareVideo from '../assets/share.mp4';
import logo from '../assets/logowhite.png';
import { client } from '../client';
const Login = () => {
const navigate = useNavigate();
const responseGoogle= (response) => {`
console.log(response)
}
return (
<div className='flex justify-start items-center flex-col h-screen'>
<div className='relative w-full h-full'>
<video
src={shareVideo}
type='video/mp4'
loop
controls={false}
muted
autoPlay
className='w-full h-full object-cover'
/>
<div className='absolute flex flex-col justify-center items-center top-0 right-0 left-0 bottom-0 bg-blackoverlay'>
<div className='p-5' >
<img src={logo} width='130px' alt='logo' />
</div>
<div className='shadow-2xl' >
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_API_TOKEN}
render={(renderProps) => (
<button
type='button'
className='bg-mainColor flex justify-center items-center p-3 rounded-lg cursor-pointer outline-none'
onClick={renderProps.onClick}
disabled={renderProps.disabled}
>
<FcGoogle className='mr-4' /> Sign in with Google
</button>
)}
onSuccess={responseGoogle}
onFailure={responseGoogle}
cookiePolicy='single_host_origin'
/>
</div>
</div>
</div>
</div>
)
}
export default Login
client.js
import sanityClient from '#sanity/client'
import imageUrlBuilder from '#sanity/image-url'
export const client = sanityClient({
projectId: process.env.REACT_APP_SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: '2021-11-16',
useCdn: true,
token: process.env.REACT_APP_SANITY_TOKEN,
ignoreBrowserTokenWarning: true
})
const builder = imageUrlBuilder(client);
export const urlFor = (source) => builder.image(source);
App.js
import React from 'react'
import { Routes, Route, useNavigate } from 'react-router-dom';
import Login from './components/Login';
import Home from './container/Home';
const App = () => {
return (
<Routes>
<Route path="Login" element={<Login />} />
<Route path="/*" element={<Home />} />
</Routes>
)
}
export default App
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router} from 'react-router-dom'
import App from './App';
import './index.css';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Router>
<App/>
</Router>,
);
.env
REACT_APP_GOOGLE_API_TOKEN = /*Sorry cannot show the token to you*/.apps.googleusercontent.com
REACT_APP_SANITY_PROJECT_ID = /*same here*/
REACT_APP_SANITY_TOKEN = /*same here*/
Home.jsx
import React from 'react'
const Home = () => {
return (
<div>
Home
</div>
)
}
export default Home
When I try to run the code it gives me this error=
TypeError: Cannot read properties of undefined (reading 'map')
I dont know how to solve this
import React from 'react'
import { Tweet } from '../typings'
import TweetBox from './TweetBox'
import TweetComponent from '../components/Tweet'
interface Props {
tweets: Tweet[]
}
function Feed({ tweets }: Props) {
return (
<div className='col-span-7 lg:col-span-5 border-x'>
<div className="flex items-center justify-between">
<h1 className="p-5 pb-0 text-xl font-bold">Home</h1>
<RefreshIcon className='h-8 w-8 cursor-pointer text-twitter mr-5 mt-5 transition-all duration-500 ease-out hover:rotate-180 active:scale-125'/>
</div>
<div>
<TweetBox />
</div>
{/* Feed */}
<div>
{tweets.map((tweet) => (
<TweetComponent key={tweet._id} tweet={tweet} />
))}
</div>
</div>
)
}
export default Feed ```
tweets array that you try to map is probably empty on render.
go to your index.tsx file and make sure when you are calling you feed component, you push the tweets prop through:
<Feed tweets={tweets}/>
here is the full index.tsx file below
import type { GetServerSideProps, NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import Feed from '../components/Feed'
import Sidebar from '../components/Sidebar'
import Widgets from '../components/Widgets'
import { Tweet } from '../typings'
import { fetchTweets } from '../utils/fetchTweets'
interface Props{
tweets: Tweet[]
}
const Home = ({tweets}: Props) => {
console.log(tweets)
return (
<div className="lg:max-w-6xl mx-auto max-h-screen overflow-hidden">
<Head>
<title>Twitter 2.0</title>
</Head>
<main className='grid grid-cols-9'>
<Sidebar/>
<Feed tweets={tweets}/>
<Widgets />
</main>
</div>
)
}
export default Home
export const getServerSideProps: GetServerSideProps = async (context) => {
const tweets = await fetchTweets()
return {
props: {
tweets,
}
}
}
I'm trying to implement vue-phone-input by wrapping it with a Quasar q-field.
It's mostly working. The input works fine and it shows validation errors underneath the input.
The problem is that I can submit the form even if there is a validation error.
How do I prevent this from happening?
Normally when using a q-form with a q-input and q-btn it will automatically stop this from happening.
So why doesn't it work here with q-field and vue-tel-input?
<template>
<q-form #submit="handlePhoneSubmit">
<q-field
v-if="isEditingPhone"
autocomplete="tel"
label="Phone"
stack-label
:error="isPhoneError"
error-message="Please enter a valid phone number."
outlined
hide-bottom-space
>
<vue-tel-input
v-model="phoneInput"
#validate="isPhoneError = !isPhoneError"
></vue-tel-input>
</q-field>
<q-btn
color="primary"
text-color="white"
no-caps
unelevated
style="max-height: 56px"
type="submit"
label="Save"
#submit="isEditingPhone = false"
/>
</q-form>
</template>
<script setup lang="ts">
import { ref, Ref } from 'vue';
import { VueTelInput } from 'vue-tel-input';
import 'vue-tel-input/dist/vue-tel-input.css';
const phone: Ref<string | null> = ref('9999 999 999');
const isEditingPhone = ref(true);
const isPhoneError = ref(false);
const phoneInput: Ref<string | null> = ref(null);
const handlePhoneSubmit = () => {
phone.value = phoneInput.value;
console.log('Form Saved');
};
</script>
First, you should use the :rules system from Quasar instead of :error and #validate
<q-field :rules="[checkPhone]"
function checkphone(value: string) {
return // validate the value here
}
Then, if the submit doesn't suffice, you may need to set a ref on your <q-form, then call its validate() method.
Here how to do it (I removed parts of the code to highlight what's required).
<template>
<q-form ref="qform" #submit="handlePhoneSubmit">
//..
</q-form>
</template>
<script setup lang="ts">
import { QForm } from "quasar";
import { ref } from "vue";
//..
const qform = ref<QForm|null>(null);
async function handlePhoneSubmit() {
if (await qform.value?.validate()) {
phone.value = phoneInput.value;
}
}
When i am learning the vue-test-utils from the offical site conditional-rendering.
I tried to change the option api to composition api.
It seems like the mount option data not working with the composition api.
Nav.vue Composition API test FAIL
<template>
<div>
<a id="profile" href="/profile">My Profile</a>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const admin = ref(false)
</script>
Nav.vue Option API test PASS
<template>
<div>
<a id="profile" href="/profile">My Profile</a>
<a v-if="admin" id="admin" href="/admin">Admin</a>
</div>
</template>
<script>
export default {
data() {
return {
admin: false,
}
},
}
</script>
Nav.spec.js test
test('renders an admin link', () => {
const wrapper = mount(Nav, {
data() {
return {
admin: true
}
}
})
// Again, by using `get()` we are implicitly asserting that
// the element exists.
expect(wrapper.get('#admin').text()).toEqual('Admin')
})
I found a solution but I don't know if it's a good solution
test("renders an admin link", async () => {
const wrapper = mount(Nav);
wrapper.vm.admin = true;
await nextTick();
expect(wrapper.get("#admin").text()).toEqual("Admin");
});