serpbear/services/keywords.tsx
towfiqi 5650645b58 feat: Adds Google Adwords Integration to allow generating Keyword Ideas.
- Integrates Google Adwords API to generate keywords for a domain.
- Adds a New Adwords Integration ui inside the App settings > Integrations screen to integrate Google Adwords.
- Adds a New Ideas tab under each domain.
- Adds ability to automatically generate keyword ideas based on website content, currently tracked keywords, Currently Ranking keywords or custom keywords
- The Keyword Ideas are not saved in database, they are saved in a local file inside the data folder. File naming convention: IDEAS_domain.com.json
- The keywords can be marked as favorites, and each time a keyword is favorited, they are added in the IDEAS_domain.com.json file.
2024-02-28 19:19:23 +06:00

194 lines
7.7 KiB
TypeScript

import toast from 'react-hot-toast';
import { NextRouter } from 'next/router';
import { useMutation, useQuery, useQueryClient } from 'react-query';
export const fetchKeywords = async (router: NextRouter, domain: string) => {
if (!domain) { return []; }
const res = await fetch(`${window.location.origin}/api/keywords?domain=${domain}`, { method: 'GET' });
return res.json();
};
export function useFetchKeywords(
router: NextRouter,
domain: string,
setKeywordSPollInterval?:Function,
keywordSPollInterval:undefined|number = undefined,
) {
const { data: keywordsData, isLoading: keywordsLoading, isError } = useQuery(
['keywords', domain],
() => fetchKeywords(router, domain),
{
refetchInterval: keywordSPollInterval,
onSuccess: (data) => {
// If Keywords are Manually Refreshed check if the any of the keywords position are still being fetched
// If yes, then refecth the keywords every 5 seconds until all the keywords position is updated by the server
if (data.keywords && data.keywords.length > 0 && setKeywordSPollInterval) {
const hasRefreshingKeyword = data.keywords.some((x:KeywordType) => x.updating);
if (hasRefreshingKeyword) {
setKeywordSPollInterval(5000);
} else {
if (keywordSPollInterval) {
toast('Keywords Refreshed!', { icon: '✔️' });
}
setKeywordSPollInterval(undefined);
}
}
},
},
);
return { keywordsData, keywordsLoading, isError };
}
export function useAddKeywords(onSuccess:Function) {
const queryClient = useQueryClient();
return useMutation(async (keywords:KeywordAddPayload[]) => {
const headers = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
const fetchOpts = { method: 'POST', headers, body: JSON.stringify({ keywords }) };
const res = await fetch(`${window.location.origin}/api/keywords`, fetchOpts);
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return res.json();
}, {
onSuccess: async () => {
console.log('Keywords Added!!!');
toast('Keywords Added Successfully!', { icon: '✔️' });
onSuccess();
queryClient.invalidateQueries(['keywords']);
},
onError: () => {
console.log('Error Adding New Keywords!!!');
toast('Error Adding New Keywords', { icon: '⚠️' });
},
});
}
export function useDeleteKeywords(onSuccess:Function) {
const queryClient = useQueryClient();
return useMutation(async (keywordIDs:number[]) => {
const keywordIds = keywordIDs.join(',');
const res = await fetch(`${window.location.origin}/api/keywords?id=${keywordIds}`, { method: 'DELETE' });
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return res.json();
}, {
onSuccess: async () => {
console.log('Removed Keyword!!!');
onSuccess();
toast('Keywords Removed Successfully!', { icon: '✔️' });
queryClient.invalidateQueries(['keywords']);
},
onError: () => {
console.log('Error Removing Keyword!!!');
toast('Error Removing the Keywords', { icon: '⚠️' });
},
});
}
export function useFavKeywords(onSuccess:Function) {
const queryClient = useQueryClient();
return useMutation(async ({ keywordID, sticky }:{keywordID:number, sticky:boolean}) => {
const headers = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
const fetchOpts = { method: 'PUT', headers, body: JSON.stringify({ sticky }) };
const res = await fetch(`${window.location.origin}/api/keywords?id=${keywordID}`, fetchOpts);
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return res.json();
}, {
onSuccess: async (data) => {
onSuccess();
const isSticky = data.keywords[0] && data.keywords[0].sticky;
toast(isSticky ? 'Keywords Made Favorite!' : 'Keywords Unfavorited!', { icon: '✔️' });
queryClient.invalidateQueries(['keywords']);
},
onError: () => {
console.log('Error Changing Favorite Status!!!');
toast('Error Changing Favorite Status.', { icon: '⚠️' });
},
});
}
export function useUpdateKeywordTags(onSuccess:Function) {
const queryClient = useQueryClient();
return useMutation(async ({ tags }:{tags:{ [ID:number]: string[] }}) => {
const keywordIds = Object.keys(tags).join(',');
const headers = new Headers({ 'Content-Type': 'application/json', Accept: 'application/json' });
const fetchOpts = { method: 'PUT', headers, body: JSON.stringify({ tags }) };
const res = await fetch(`${window.location.origin}/api/keywords?id=${keywordIds}`, fetchOpts);
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return res.json();
}, {
onSuccess: async () => {
onSuccess();
toast('Keyword Tags Updated!', { icon: '✔️' });
queryClient.invalidateQueries(['keywords']);
},
onError: () => {
console.log('Error Updating Keyword Tags!!!');
toast('Error Updating Keyword Tags.', { icon: '⚠️' });
},
});
}
export function useRefreshKeywords(onSuccess:Function) {
const queryClient = useQueryClient();
return useMutation(async ({ ids = [], domain = '' } : {ids?: number[], domain?: string}) => {
const keywordIds = ids.join(',');
console.log(keywordIds);
const query = ids.length === 0 && domain ? `?id=all&domain=${domain}` : `?id=${keywordIds}`;
const res = await fetch(`${window.location.origin}/api/refresh${query}`, { method: 'POST' });
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return res.json();
}, {
onSuccess: async () => {
console.log('Keywords Added to Refresh Queue!!!');
onSuccess();
toast('Keywords Added to Refresh Queue', { icon: '🔄' });
queryClient.invalidateQueries(['keywords']);
},
onError: () => {
console.log('Error Refreshing Keywords!!!');
toast('Error Refreshing Keywords.', { icon: '⚠️' });
},
});
}
export function useFetchSingleKeyword(keywordID:number) {
return useQuery(['keyword', keywordID], async () => {
try {
const fetchURL = `${window.location.origin}/api/keyword?id=${keywordID}`;
const res = await fetch(fetchURL, { method: 'GET' }).then((result) => result.json());
if (res.status >= 400 && res.status < 600) {
throw new Error('Bad response from server');
}
return { history: res.keyword.history || [], searchResult: res.keyword.lastResult || [] };
} catch (error) {
throw new Error('Error Loading Keyword Details');
}
}, {
onError: () => {
console.log('Error Loading Keyword Data!!!');
toast('Error Loading Keyword Details.', { icon: '⚠️' });
},
});
}
export async function fetchSearchResults(router:NextRouter, keywordData: Record<string, string>) {
const { keyword, country, device } = keywordData;
const res = await fetch(`${window.location.origin}/api/refresh?keyword=${keyword}&country=${country}&device=${device}`, { method: 'GET' });
if (res.status >= 400 && res.status < 600) {
if (res.status === 401) {
console.log('Unauthorized!!');
router.push('/login');
}
throw new Error('Bad response from server');
}
return res.json();
}