/**
* BoostMedia AI Content Generator Admin - Configure Step
*
* @package BoostMedia_AI
* @license GPL-2.0-or-later
*/
import { useMemo } from 'react'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import type { GenerationConfig } from './SelectPostTypeStep'
import { Button, Card, HelpTooltip } from '../common'
import { TagInput } from './TagInput'
import { t, isRtl } from '../../lib/i18n'
interface ConfigureStepProps {
config: GenerationConfig
onUpdate: (config: GenerationConfig) => void
onBack: () => void
onNext: () => void
}
const countPresets = [10, 50, 100, 500, 1000]
const weekdayOptions = [
{ value: 0, label: t('Sun') },
{ value: 1, label: t('Mon') },
{ value: 2, label: t('Tue') },
{ value: 3, label: t('Wed') },
{ value: 4, label: t('Thu') },
{ value: 5, label: t('Fri') },
{ value: 6, label: t('Sat') },
]
function ToggleSwitch({
checked,
onChange,
}: {
checked: boolean
onChange: () => void
}) {
return (
)
}
export interface ConfigureContentProps {
config: GenerationConfig
onUpdate: (config: GenerationConfig) => void
}
export function ConfigureContent({
config,
onUpdate,
}: ConfigureContentProps) {
const selectedTermCount = config.term ? config.term.split(',').filter(Boolean).length : 0
const hasMultipleCategories = selectedTermCount > 1
const lengthOptions = useMemo(() => [
{ value: 'auto' as const, label: t('AI decides'), description: t('Based on samples') },
{ value: 'short' as const, label: t('Short'), description: t('~300 words') },
{ value: 'medium' as const, label: t('Medium'), description: t('~600 words') },
{ value: 'long' as const, label: t('Long'), description: t('~1000 words') },
], [])
const repeatSummary = useMemo(() => {
if (config.generationType !== 'repeating') {
return ''
}
const unitLabel = config.repeatUnit === 'day'
? t(config.repeatEvery === 1 ? 'day' : 'days')
: config.repeatUnit === 'month'
? t(config.repeatEvery === 1 ? 'month' : 'months')
: t(config.repeatEvery === 1 ? 'week' : 'weeks')
const weekdayLabel = config.repeatUnit === 'week' && config.repeatWeekdays.length > 0
? ` • ${config.repeatWeekdays.map((weekday) => weekdayOptions.find((option) => option.value === weekday)?.label || weekday).join(', ')}`
: ''
const budgetParts = [
config.maxTotalRuns ? `${t('Up to')} ${config.maxTotalRuns} ${t(config.maxTotalRuns === 1 ? 'run' : 'runs')}` : '',
config.maxTotalPosts ? `${t('Up to')} ${config.maxTotalPosts} ${t(config.maxTotalPosts === 1 ? 'post' : 'posts')}` : '',
config.stopAfterMonths ? `${t('Stop after')} ${config.stopAfterMonths} ${t(config.stopAfterMonths === 1 ? 'month' : 'months')}` : '',
].filter(Boolean)
return `${t('Every')} ${config.repeatEvery > 1 ? `${config.repeatEvery} ` : ''}${unitLabel}`.replace(/\s+/g, ' ').trim()
+ `${weekdayLabel} • ${String(config.repeatHour).padStart(2, '0')}:00`
+ (budgetParts.length > 0 ? ` • ${budgetParts.join(' • ')}` : '')
}, [config.generationType, config.maxTotalPosts, config.maxTotalRuns, config.repeatEvery, config.repeatHour, config.repeatUnit, config.repeatWeekdays, config.stopAfterMonths])
const updateNullablePositiveNumber = (
key: 'stopAfterMonths' | 'maxTotalRuns' | 'maxTotalPosts',
value: string,
) => {
const normalized = value.trim()
onUpdate({
...config,
[key]: normalized === '' ? null : Math.max(1, parseInt(normalized, 10) || 1),
})
}
return (
{t('Plan name')}
{t('Use a clear internal name so you can manage this plan later from the Content Plans dashboard.')}
onUpdate({ ...config, planName: e.target.value })}
placeholder={t('e.g.: Fire pumps monthly blog plan')}
className="
w-full p-3 rounded-bc
border border-bc-gray-300
bg-white text-bc-gray-800
placeholder:text-bc-gray-400
focus:ring-2 focus:ring-bc-primary focus:border-bc-primary
transition-all duration-200
"
/>
{t('Plan execution')}
{t('Choose whether this plan runs once or keeps running automatically, and set its publishing behavior in the same place.')}
{[
{
value: 'one_time' as const,
label: t('One-time plan'),
description: t('Run this plan manually whenever you choose'),
},
{
value: 'repeating' as const,
label: t('Repeating plan'),
description: t('Keep this plan active and let WordPress run it on a schedule'),
},
].map((option) => (
onUpdate({
...config,
generationType: option.value,
})}
className={`
rounded-bc border-2 p-4 text-start transition-all duration-150 bc-focus-visible
${config.generationType === option.value
? 'border-bc-primary bg-bc-primary-light'
: 'border-bc-gray-200 bg-white hover:border-bc-gray-300'
}
`}
>
{option.label}
{option.description}
))}
{config.generationType === 'repeating' ? (
{t('Repeat every')}
onUpdate({ ...config, repeatEvery: Math.max(1, parseInt(e.target.value, 10) || 1) })}
className="w-full rounded-bc border border-bc-gray-300 px-3 py-2"
/>
{t('Time unit')}
{
const nextUnit = e.target.value as GenerationConfig['repeatUnit']
onUpdate({
...config,
repeatUnit: nextUnit,
repeatFrequency: nextUnit === 'month' ? 'monthly' : nextUnit === 'day' ? 'daily' : 'weekly',
repeatWeekdays: nextUnit === 'week' ? config.repeatWeekdays : [],
})
}}
className="w-full rounded-bc border border-bc-gray-300 px-3 py-2"
>
{t('Day')}
{t('Week')}
{t('Month')}
{t('Run hour')}
onUpdate({ ...config, repeatHour: Number(e.target.value) })}
className="w-full rounded-bc border border-bc-gray-300 px-3 py-2"
>
{Array.from({ length: 24 }, (_, hour) => (
{String(hour).padStart(2, '0')}:00
))}
{config.repeatUnit === 'week' ? (
{t('Run on weekday(s)')}
{weekdayOptions.map((weekday) => {
const selected = config.repeatWeekdays.includes(weekday.value)
return (
onUpdate({
...config,
repeatWeekdays: selected
? config.repeatWeekdays.filter((value) => value !== weekday.value)
: [...config.repeatWeekdays, weekday.value].sort((a, b) => a - b),
})}
className={`rounded-full border px-3 py-1.5 text-sm transition-colors ${
selected
? 'border-bc-primary bg-bc-primary-light text-bc-primary'
: 'border-bc-gray-200 bg-white text-bc-gray-600 hover:border-bc-gray-300'
}`}
>
{weekday.label}
)
})}
{t('Leave all weekdays unselected to run on the same weekday cadence as the plan start date.')}
) : null}
{t('Schedule summary')}
{repeatSummary}
onUpdate({ ...config, isActive: !config.isActive })}
/>
{t('Plan active')}
{t('Only active repeating plans will run automatically')}
) : (
{t('Run this plan manually whenever you choose')}
)}
{/* Topic — textarea */}
{t('Topic / Instructions')} *
{t('Describe what the batch of posts should be about. Be as specific or general as you like — AI will understand.')}
{/* Keywords */}
{t('Keywords')}
{t('Add keywords to be included in the content (press Enter to add)')}
onUpdate({ ...config, keywords })}
placeholder={t('Add keyword...')}
/>
{/* Count — presets + number input */}
{t('Number of posts')}
{t('How many posts to create on this topic?')}
{countPresets.map((count) => (
onUpdate({ ...config, count })}
className={`
px-5 py-2.5 rounded-bc font-semibold text-sm
transition-all duration-150 bc-focus-visible
${
config.count === count
? 'bg-bc-primary text-white shadow-md'
: 'bg-bc-gray-100 text-bc-gray-700 hover:bg-bc-gray-200'
}
`}
>
{count}
))}
onUpdate({ ...config, count: Math.min(1000, Math.max(1, parseInt(e.target.value) || 1)) })}
className="w-32 px-3 py-2 border border-bc-gray-300 rounded-bc text-sm focus:border-bc-primary focus:ring-1 focus:ring-bc-primary outline-none transition-colors"
/>
{t('Each plan can generate up to 1,000 posts.')}
{/* Per-category toggle */}
{hasMultipleCategories && (
onUpdate({ ...config, countPerCategory: !config.countPerCategory })}
/>
{config.countPerCategory ? t('Per category') : t('Total')}
{config.countPerCategory
? t('Create this many posts for EACH selected category')
: t('Create this many posts total, distributed across selected categories')
}
)}
{/* Publishing */}
{t('After creation')}
{t('What should happen to posts after they are generated?')}
{([
{ value: 'none' as const, label: t('Save as draft'), description: t('Save as draft for manual review') },
{ value: 'immediate' as const, label: t('Publish immediately'), description: t('Publish right after creation') },
{ value: 'daily' as const, label: t('Drip daily'), description: t('Publish one post each day') },
{ value: 'weekly' as const, label: t('Drip weekly'), description: t('Publish one post each week') },
] as const).map((option) => (
onUpdate({ ...config, scheduling: option.value })}
className={`
p-3 rounded-bc text-start
transition-all duration-150 bc-focus-visible
border-2
${
config.scheduling === option.value
? 'border-bc-primary bg-bc-primary-light'
: 'border-bc-gray-200 bg-white hover:border-bc-gray-300'
}
`}
>
{option.label}
{option.description}
))}
{/* Length */}
{t('Post length')}
{lengthOptions.map((option) => (
onUpdate({ ...config, length: option.value })}
className={`
p-4 rounded-bc text-center
transition-all duration-150 bc-focus-visible
border-2
${
config.length === option.value
? 'border-bc-primary bg-bc-primary-light'
: 'border-bc-gray-200 bg-white hover:border-bc-gray-300'
}
`}
>
{option.label}
{option.description}
))}
)
}
export function ConfigureStep({
config,
onUpdate,
onBack,
onNext,
}: ConfigureStepProps) {
const isValid = config.topic.trim().length > 0
return (
{t('Generation Settings')}
{t('Set the details for the content to be created')}
}
iconPosition={isRtl() ? 'end' : 'start'}
>
{t('Back')}
}
iconPosition={isRtl() ? 'start' : 'end'}
>
{t('Next step')}
)
}