Skip to content

Commit

Permalink
feat: Adds Google Adwords Integration to allow generating Keyword Ideas.
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
towfiqi committed Feb 28, 2024
1 parent 83c4745 commit 5650645
Show file tree
Hide file tree
Showing 33 changed files with 3,509 additions and 305 deletions.
5 changes: 3 additions & 2 deletions components/common/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ type ChartProps ={
labels: string[],
sreies: number[],
reverse? : boolean,
noMaxLimit?: boolean
}

const Chart = ({ labels, sreies, reverse = true }:ChartProps) => {
const Chart = ({ labels, sreies, reverse = true, noMaxLimit = false }:ChartProps) => {
const options = {
responsive: true,
maintainAspectRatio: false,
Expand All @@ -19,7 +20,7 @@ const Chart = ({ labels, sreies, reverse = true }:ChartProps) => {
y: {
reverse,
min: 1,
max: reverse ? 100 : undefined,
max: !noMaxLimit && reverse ? 100 : undefined,
},
},
plugins: {
Expand Down
7 changes: 4 additions & 3 deletions components/common/ChartSlim.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Filler,

type ChartProps ={
labels: string[],
sreies: number[]
sreies: number[],
noMaxLimit?: boolean
}

const ChartSlim = ({ labels, sreies }:ChartProps) => {
const ChartSlim = ({ labels, sreies, noMaxLimit = false }:ChartProps) => {
const options = {
responsive: true,
maintainAspectRatio: false,
Expand All @@ -19,7 +20,7 @@ const ChartSlim = ({ labels, sreies }:ChartProps) => {
display: false,
reverse: true,
min: 1,
max: 100,
max: noMaxLimit ? undefined : 100,
},
x: {
display: false,
Expand Down
28 changes: 24 additions & 4 deletions components/common/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,13 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
}
{type === 'star'
&& <svg width={size} viewBox="0 0 24 24" {...xmlnsProps}>
<path fill={color} d="M10 1L7 7l-6 .75l4.13 4.62L4 19l6-3l6 3l-1.12-6.63L19 7.75L13 7zm0 2.24l2.34 4.69l4.65.58l-3.18 3.56l.87 5.15L10 14.88l-4.68 2.34l.87-5.15l-3.18-3.56l4.65-.58z"/>
</svg>
<path fill={color} d="m12 15.39l-3.76 2.27l.99-4.28l-3.32-2.88l4.38-.37L12 6.09l1.71 4.04l4.38.37l-3.32 2.88l.99 4.28M22 9.24l-7.19-.61L12 2L9.19 8.63L2 9.24l5.45 4.73L5.82 21L12 17.27L18.18 21l-1.64-7.03z"></path>
</svg>
}
{type === 'star-filled'
&& <svg width={size} viewBox="0 0 24 24" {...xmlnsProps}>
<path fill={color} d="M10 1l3 6l6 .75l-4.12 4.62L16 19l-6-3l-6 3l1.13-6.63L1 7.75L7 7z"/>
</svg>
<path fill={color} d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2L9.19 8.62L2 9.24l5.45 4.73L5.82 21z"></path>
</svg>
}
{type === 'link'
&& <svg width={size} viewBox="0 0 20 20" {...xmlnsProps}>
Expand Down Expand Up @@ -208,6 +208,26 @@ const Icon = ({ type, color = 'currentColor', size = 16, title = '', classes = '
<path d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0C79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251" fill="#EB4335" />
</svg>
}
{type === 'adwords'
&& <svg {...xmlnsProps} width={size} viewBox="0 0 256 256">
<g>
<path d="M5.888,166.405103 L90.88,20.9 C101.676138,27.2558621 156.115862,57.3844138 164.908138,63.1135172 L79.9161379,208.627448 C70.6206897,220.906621 -5.888,185.040138 5.888,166.396276 L5.888,166.405103 Z" fill="#FBBC04"></path>
<path d="M250.084224,166.401789 L165.092224,20.9055131 C153.210293,1.13172 127.619121,-6.05393517 106.600638,5.62496138 C85.582155,17.3038579 79.182155,42.4624786 91.0640861,63.1190303 L176.056086,208.632961 C187.938017,228.397927 213.52919,235.583582 234.547672,223.904686 C254.648086,212.225789 261.966155,186.175582 250.084224,166.419444 L250.084224,166.401789 Z" fill="#4285F4"></path>
<ellipse fill="#34A853" cx="42.6637241" cy="187.924414" rx="42.6637241" ry="41.6044138"></ellipse>
</g>
</svg>
}
{type === 'keywords'
&& <svg {...xmlnsProps} width={size} viewBox="0 0 24 24">
<path fill="none" stroke={color} strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 8h14M5 12h14M5 16h6"></path>
</svg>
}
{type === 'integration'
&& <svg {...xmlnsProps} width={size} viewBox="0 0 24 24">
<path fill="none" stroke={color} strokeWidth={2} d="M10 21c-2.5 2.5-5 2-7 0s-2.5-4.5 0-7l3-3l7 7zm4-18c2.5-2.5 5-2 7.001 0c2.001 2 2.499 4.5 0 7l-3 3L11 6zm-3 7l-2.5 2.5zm3 3l-2.5 2.5z"></path>
</svg>
}

{type === 'cursor'
&& <svg {...xmlnsProps} width={size} viewBox="0 0 24 24">
<path fill="none" stroke={color} strokeWidth="2" d="M6 3l12 11l-5 1l3 5.5l-3 1.5l-3-6l-4 3z"/>
Expand Down
2 changes: 1 addition & 1 deletion components/common/SecretField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const SecretField = ({ label = '', value = '', placeholder = '', onChange, hasEr
const [showValue, setShowValue] = useState(false);
const labelStyle = 'mb-2 font-semibold inline-block text-sm text-gray-700 capitalize';
return (
<div className="settings__section__secret mb-5 relative flex justify-between items-center">
<div className="settings__section__secret w-full relative flex justify-between items-center">
<label className={labelStyle}>{label}</label>
<span
className="absolute top-1 right-0 px-2 py-1 cursor-pointer text-gray-400 select-none"
Expand Down
8 changes: 5 additions & 3 deletions components/common/SelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type SelectFieldProps = {
label?: string,
multiple?: boolean,
updateField: Function,
fullWidth?: boolean,
minWidth?: number,
maxHeight?: number|string,
rounded?: string,
Expand All @@ -27,6 +28,7 @@ const SelectField = (props: SelectFieldProps) => {
updateField,
minWidth = 180,
maxHeight = 96,
fullWidth = false,
rounded = 'rounded-3xl',
flags = false,
label = '',
Expand Down Expand Up @@ -71,8 +73,8 @@ const SelectField = (props: SelectFieldProps) => {
<div className="select font-semibold text-gray-500 relative flex justify-between items-center">
{label && <label className='mb-2 font-semibold inline-block text-sm text-gray-700 capitalize'>{label}</label>}
<div
className={`selected flex border ${rounded} p-1.5 px-4 cursor-pointer select-none w-[210px] min-w-[${minWidth}px]
${showOptions ? 'border-indigo-200' : ''}`}
className={`selected flex border ${rounded} p-1.5 px-4 cursor-pointer select-none ${fullWidth ? 'w-full' : 'w-[210px]'}
min-w-[${minWidth}px] ${showOptions ? 'border-indigo-200' : ''}`}
onClick={() => setShowOptions(!showOptions)}>
<span className={'w-full inline-block truncate mr-2 capitalize'}>
{selected.length > 0 ? (selectedLabels.slice(0, 2).join(', ')) : defaultLabel}
Expand All @@ -83,7 +85,7 @@ const SelectField = (props: SelectFieldProps) => {
</div>
{showOptions && (
<div
className={`select_list mt-1 border absolute min-w-[${minWidth}px] top-[30px] right-0 w-[210px]
className={`select_list mt-1 border absolute min-w-[${minWidth}px] top-[30px] right-0 ${fullWidth ? 'w-full' : 'w-[210px]'}
${rounded === 'rounded-3xl' ? 'rounded-lg' : rounded} max-h-${maxHeight} bg-white z-50 overflow-y-auto styled-scrollbar`}>
{options.length > 20 && (
<div className=''>
Expand Down
2 changes: 1 addition & 1 deletion components/common/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Sidebar = ({ domains, showAddModal } : SidebarProps) => {
<Link href={`/domain/${d.slug}`} passHref={true}>
<a className={`block cursor-pointer px-4 text-ellipsis max-w-[215px] overflow-hidden whitespace-nowrap rounded
rounded-r-none ${((`/domain/${d.slug}` === router.asPath || `/domain/console/${d.slug}` === router.asPath
|| `/domain/insight/${d.slug}` === router.asPath)
|| `/domain/insight/${d.slug}` === router.asPath || `/domain/ideas/${d.slug}` === router.asPath)
? 'bg-white text-zinc-800 border border-r-0' : 'text-zinc-500')}`}>
<img
className={' inline-block mr-1'}
Expand Down
8 changes: 4 additions & 4 deletions components/common/ToggleField.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
type ToggleFieldProps = {
label: string;
value: string;
value: boolean;
onChange: (bool:boolean) => void ;
classNames?: string;
}

const ToggleField = ({ label = '', value = '', onChange, classNames = '' }: ToggleFieldProps) => {
const ToggleField = ({ label = '', value = false, onChange, classNames = '' }: ToggleFieldProps) => {
return (
<div className={`field--toggle w-full relative ${classNames}`}>
<label className="relative inline-flex items-center cursor-pointer w-full justify-between">
<span className="text-sm font-medium text-gray-900 dark:text-gray-300 w-56">{label}</span>
<span className="text-sm font-medium text-gray-900 dark:text-gray-300 w-auto">{label}</span>
<input
type="checkbox"
value={value}
value={value.toString()}
checked={!!value}
className="sr-only peer"
onChange={() => onChange(!value)}
Expand Down
44 changes: 37 additions & 7 deletions components/domains/DomainHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ type DomainHeaderProps = {
exportCsv:Function,
scFilter?: string
setScFilter?: Function
showIdeaUpdateModal?:Function
}

const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, domains, scFilter = 'thirtyDays', setScFilter }: DomainHeaderProps) => {
const DomainHeader = (
{ domain, showAddModal, showSettingsModal, exportCsv, domains, scFilter = 'thirtyDays', setScFilter, showIdeaUpdateModal }: DomainHeaderProps,
) => {
const router = useRouter();
const [showOptions, setShowOptions] = useState<boolean>(false);
const [ShowSCDates, setShowSCDates] = useState<boolean>(false);
const { mutate: refreshMutate } = useRefreshKeywords(() => {});
const isConsole = router.pathname === '/domain/console/[slug]';
const isInsight = router.pathname === '/domain/insight/[slug]';
const isIdeas = router.pathname === '/domain/ideas/[slug]';

const daysName = (dayKey:string) => dayKey.replace('three', '3').replace('seven', '7').replace('thirty', '30').replace('Days', ' Days');
const buttonStyle = 'leading-6 inline-block px-2 py-2 text-gray-500 hover:text-gray-700';
Expand All @@ -45,8 +49,8 @@ const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, doma
/>
</div>
</div>
<div className='flex w-full justify-between'>
<ul className=' flex items-end text-sm relative top-[2px]'>
<div className='flex w-full justify-between mt-4 lg:mt-0'>
<ul className=' max-w-[270px] overflow-auto flex items-end text-sm relative top-[2px] lg:max-w-none'>
<li className={`${tabStyle} ${router.pathname === '/domain/[slug]' ? 'bg-white border border-b-0 font-semibold' : ''}`}>
<Link href={`/domain/${domain.slug}`} passHref={true}>
<a className='px-4 py-2 inline-block'><Icon type="tracking" color='#999' classes='hidden lg:inline-block' />
Expand All @@ -70,8 +74,22 @@ const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, doma
</a>
</Link>
</li>
<li className={`${tabStyle} ${router.pathname === '/domain/ideas/[slug]' ? 'bg-white border border-b-0 font-semibold' : ''}`}>
<Link href={`/domain/ideas/${domain.slug}`} passHref={true}>
<a className='px-4 py-2 inline-block'><Icon type="adwords" size={13} classes='hidden lg:inline-block' />
<span className='text-xs lg:text-sm lg:ml-2'>Ideas</span>
<Icon
type='help'
size={14}
color="#aaa"
classes="ml-2 hidden lg:inline-block"
title='Get Keyword Ideas for this domain from Google Adwords'
/>
</a>
</Link>
</li>
</ul>
<div className={'flex mt-3 mb-0 lg:mb-3'}>
<div className={'flex mb-0 lg:mb-1 lg:mt-3'}>
{!isInsight && <button className={`${buttonStyle} lg:hidden`} onClick={() => setShowOptions(!showOptions)}>
<Icon type='dots' size={20} />
</button>
Expand All @@ -89,7 +107,7 @@ const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, doma
<Icon type='download' size={20} /><i className={`${buttonLabelStyle}`}>Export as csv</i>
</button>
)}
{!isConsole && !isInsight && (
{!isConsole && !isInsight && !isIdeas && (
<button
className={`domheader_action_button relative ${buttonStyle} lg:ml-3`}
aria-pressed="false"
Expand All @@ -105,10 +123,10 @@ const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, doma
<i className={`${buttonLabelStyle}`}>Domain Settings</i>
</button>
</div>
{!isConsole && !isInsight && (
{!isConsole && !isInsight && !isIdeas && (
<button
data-testid="add_keyword"
className={'ml-2 inline-block px-4 py-2 text-blue-700 font-bold text-sm'}
className={'ml-2 inline-block text-blue-700 font-bold text-sm lg:px-4 lg:py-2'}
onClick={() => showAddModal(true)}>
<span
className='text-center leading-4 mr-2 inline-block rounded-full w-7 h-7 pt-1 bg-blue-700 text-white font-bold text-lg'>+</span>
Expand All @@ -135,6 +153,18 @@ const DomainHeader = ({ domain, showAddModal, showSettingsModal, exportCsv, doma
)}
</div>
)}
{isIdeas && (
<button
data-testid="load_ideas"
className={'ml-2 text-blue-700 font-bold text-sm flex items-center lg:px-4 lg:py-2'}
onClick={() => showIdeaUpdateModal && showIdeaUpdateModal()}>
<span
className='text-center leading-4 mr-2 inline-block rounded-full w-7 h-7 pt-1 bg-blue-700 text-white font-bold text-lg'>
<Icon type='reload' size={12} />
</span>
<i className=' not-italic hidden lg:inline-block'>Load Ideas</i>
</button>
)}
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit 5650645

Please sign in to comment.