import { useEffect, useMemo, useRef, useState, type ReactNode, type Ref } from 'react'; import { getErrorSummary } from '../../api/errors'; import { appViewHref } from '../../lib/appUrl'; import { useAdminRouting } from '../../lib/adminRouting'; import { createDraftCourse } from '../../lib/createCourse'; import { slugFromTitle } from '../../lib/slugFromTitle'; import { sikshyaPricingUrl } from '../../lib/upgradeUrl'; import type { SikshyaReactConfig } from '../../types'; import { isFeatureEnabled } from '../../lib/licensing'; import { useAddonEnabled } from '../../hooks/useAddons'; import { ButtonPrimary } from './buttons'; import { Modal } from './Modal'; import { term, termLower } from '../../lib/terminology'; import { __, sprintf } from '../../lib/i18n'; type Props = { config: SikshyaReactConfig; open: boolean; onClose: () => void; }; type CourseKind = 'regular' | 'bundle'; const FIELD_LABEL = 'block text-sm font-medium text-slate-800 dark:text-slate-200'; const FIELD_HINT = 'mt-1 text-xs leading-relaxed text-slate-500 dark:text-slate-400'; const FIELD_INPUT = 'w-full rounded-xl border border-slate-200 bg-white px-3.5 py-2.5 text-sm text-slate-900 shadow-sm placeholder:text-slate-400 transition focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20 dark:border-slate-600 dark:bg-slate-800 dark:text-white dark:placeholder:text-slate-500'; export function CreateCourseModal({ config, open, onClose }: Props) { const { navigateHref } = useAdminRouting(); const [kind, setKind] = useState('regular'); const [title, setTitle] = useState(''); const [slug, setSlug] = useState(''); const [slugManual, setSlugManual] = useState(false); const [slugEditing, setSlugEditing] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const slugInputRef = useRef(null); const bundlesFeatureOk = isFeatureEnabled(config, 'course_bundles'); const bundlesAddon = useAddonEnabled('course_bundles'); const courseLower = termLower(config, 'course'); const courseTitle = term(config, 'course'); const coursesLower = termLower(config, 'courses'); const brandName = config.branding?.pluginName?.trim() || 'Sikshya'; const canUseBundles = useMemo(() => { if (!bundlesFeatureOk) return false; if (bundlesAddon.loading) return false; return Boolean(bundlesAddon.enabled); }, [bundlesAddon.enabled, bundlesAddon.loading, bundlesFeatureOk]); const siteRoot = config.siteUrl.replace(/\/$/, ''); const courseBase = (config.permalinks && config.permalinks.rewrite_base_course) || 'courses'; const coursePostType = (config.postTypes && config.postTypes.course) || 'sik_course'; const effectiveSlug = slug.trim() || 'your-course'; const permalinkPreview = (() => { if (config.plainPermalinks) { return `${siteRoot}/?post_type=${coursePostType}&name=${effectiveSlug}`; } return `${siteRoot}/${courseBase}/${effectiveSlug}/`; })(); useEffect(() => { if (open) { setKind('regular'); setTitle(''); setSlug(''); setSlugManual(false); setSlugEditing(false); setError(null); setSubmitting(false); } }, [open]); useEffect(() => { if (!slugEditing) { return; } const t = window.setTimeout(() => { slugInputRef.current?.focus(); slugInputRef.current?.select(); }, 0); return () => window.clearTimeout(t); }, [slugEditing]); const reset = () => { setKind('regular'); setTitle(''); setSlug(''); setSlugManual(false); setSlugEditing(false); setError(null); setSubmitting(false); }; const handleClose = () => { if (!submitting) { reset(); onClose(); } }; const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSubmitting(true); try { const safeKind: CourseKind = kind === 'bundle' && !canUseBundles ? 'regular' : kind; const requestedSlug = slug.trim(); const { id, slug: finalSlug } = await createDraftCourse(title, { slug: requestedSlug || undefined, kind: safeKind, }); // If the user explicitly typed a slug and the server stored a different // one (collision → suffix, or character sanitization), leave a one-shot // flash message for the builder to surface — otherwise their URL has // silently changed under them. if (requestedSlug && finalSlug && finalSlug !== requestedSlug) { try { window.sessionStorage.setItem( `sikshya_course_builder_slug_notice_${id}`, JSON.stringify({ requested: requestedSlug, final: finalSlug }) ); } catch { /* private mode / quota — fall through */ } } const extra: Record = { course_id: String(id) }; if (safeKind === 'bundle') { extra.force_bundle_ui = '1'; } const url = appViewHref(config, 'add-course', extra); navigateHref(url); } catch (err) { setError(getErrorSummary(err)); setSubmitting(false); } }; const upgradeHref = sikshyaPricingUrl('addon-enable-upgrade', 'course_bundles'); const finishSlugEdit = () => { setSlugEditing(false); if (!slug.trim() && title.trim()) { setSlug(slugFromTitle(title)); setSlugManual(false); } }; const regularCourseLabel = sprintf(__('Regular %s', 'sikshya'), courseTitle); const courseBundleLabel = sprintf(__('%s bundle', 'sikshya'), courseTitle); const titlePlaceholder = kind === 'bundle' && canUseBundles ? __('e.g. Full Stack Bootcamp', 'sikshya') : __('e.g. WordPress for Beginners', 'sikshya'); const nameFieldLabel = kind === 'bundle' && canUseBundles ? sprintf(__('%s name', 'sikshya'), courseBundleLabel) : sprintf(__('%s name', 'sikshya'), courseTitle); return (

{__('Starts as a draft — nothing is public until you publish.', 'sikshya')}

{submitting ? __('Creating…', 'sikshya') : __('Create & continue', 'sikshya')}
} >

{__('Choose a format', 'sikshya')}

{canUseBundles ? sprintf( __('Click a card to select — %1$s or %2$s.', 'sikshya'), regularCourseLabel, courseBundleLabel ) : sprintf( __('Click a card to select. With %1$s Pro, you can choose %2$s too.', 'sikshya'), brandName, courseBundleLabel )}

setKind('regular')} disabled={submitting} icon={} title={regularCourseLabel} description={sprintf( __('One %s with lessons, videos, and quizzes.', 'sikshya'), courseLower )} /> {canUseBundles ? ( setKind('bundle')} disabled={submitting} icon={} title={courseBundleLabel} description={sprintf( __('Package existing %s and sell them with one checkout.', 'sikshya'), coursesLower )} /> ) : ( )}
{ const v = e.target.value; setTitle(v); if (!slugManual) { setSlug(v.trim() ? slugFromTitle(v) : ''); } }} placeholder={titlePlaceholder} disabled={submitting} autoFocus className={`${FIELD_INPUT} mt-1.5`} />
{__('Permalink', 'sikshya')}

{sprintf(__('The web address where learners open this %s.', 'sikshya'), courseLower)}

setSlugEditing(true)} onFinishEdit={finishSlugEdit} onSlugChange={(v) => { setSlugManual(true); setSlug(v); }} />
{error ? (
{error}
) : null}
); } function PermalinkField({ editing, url, slug, disabled, inputRef, onStartEdit, onFinishEdit, onSlugChange, }: { editing: boolean; url: string; slug: string; disabled?: boolean; inputRef: Ref; onStartEdit: () => void; onFinishEdit: () => void; onSlugChange: (slug: string) => void; }) { if (editing) { return (
{ const v = e.target.value .toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, ''); onSlugChange(v); }} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); onFinishEdit(); } if (e.key === 'Escape') { e.preventDefault(); onFinishEdit(); } }} className="min-w-0 flex-1 border-0 bg-transparent px-3 py-2.5 font-mono text-xs text-slate-900 outline-none focus:ring-0 dark:text-white" />

{sprintf(__('Preview: %s', 'sikshya'), url)}

); } return (
{url}
); } function FormatChoiceCard({ selected, onSelect, disabled, icon, title, description, }: { selected: boolean; onSelect: () => void; disabled?: boolean; icon: ReactNode; title: string; description: string; }) { return ( ); } function BundleFormatCardLocked({ bundleLabel, brandName, coursesLower, upgradeHref, loading, }: { bundleLabel: string; brandName: string; coursesLower: string; upgradeHref: string; loading: boolean; }) { return (
{bundleLabel} Pro

{sprintf(__('Package existing %s —', 'sikshya'), coursesLower)}{' '} {loading ? ( {__('checking plan…', 'sikshya')} ) : ( {__('Upgrade to Pro to select', 'sikshya')} )}

); } function SelectionCheck({ selected }: { selected: boolean }) { return ( {selected ? ( ) : null} ); } function RegularIcon() { return ( ); } function BundleIcon({ className = 'h-4 w-4' }: { className?: string }) { return ( ); } function GlobeIcon() { return ( ); } function DraftIcon() { return ( ); }